NeetCode刷题第18天(2025.1.30)

089 Redundant Connection 冗余连接

为您提供了一个连接的无向图,其中 n 个节点从 1 标记到 n 。最初,它没有循环,由 n-1 边缘组成。
现在,我们为图表添加了一个额外的边缘。边缘具有从 1 到 n 的两个不同的顶点,并且不是图中以前存在的边缘。
该图表示为长度 n 的数组 edges ,其中 edges[i] = [ai, bi] 表示节点 ai 和 bi 在图中。
返回可以删除的边缘,以使该图仍然是连接的非周期图。如果有多个答案,请返回输入 edges 中最后出现的边缘。

在这里插入图片描述

示例1:
Input: edges = [[1,2],[1,3],[3,4],[2,4]]
Output: [2,4]

在这里插入图片描述

示例2:
Input: edges = [[1,2],[1,3],[1,4],[3,4],[4,5]]
Output: [3,4]

解题1: Disjoint Set Union
在这里插入图片描述
当我们有n个节点和n条边的时候,这之间一定存在循环部分。
在这里插入图片描述
这里我们有par和rank两个列表,一个是用来记录根节点,一个用来标记等级的,没添加一个节点,跟节点等级+1,把小的rank加到大的rank上。
在这里插入图片描述
当我们把前面两条边相连的时候,可以看到par列表都是1了,说明他们都是共同的根节点了,此时他们已经是连通的了。所以最后一条边就是冗余的了。

class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        par = [ i for i in range(len(edges) + 1)]
        rank = [1] * (len(edges) + 1)

        def find(n):
            p = par[n]
            while p != par[p]:
                par[p] = par[par[p]]
                p = par[p]
            return p

        # return False if cannot complete
        def union(n1, n2):
            p1, p2 = find(n1), find(n2)

            if p1 == p2:
                return False
            
            if rank[p1] > rank[p2]:
                par[p2] = p1
                rank[p1] += rank[p2]
            else:
                par[p1] = p2
                rank[p2] += rank[p1]
            return True
        
        for n1, n2 in edges:
            if not union(n1, n2):
                return [n1, n2]

时间复杂度: O ( V + ( E ∗ α ( V ) ) O(V+(E∗α(V)) O(V+(Eα(V))
空间复杂度: O ( V ) O(V) O(V)
V 是顶点数,E 是边的数量。

090 Network Delay Time 网络延迟时间

给您一个 n 定向节点的网络,该节点从 1 标记为 n 。您还将给出 times ,这是有向边的列表,其中 times[i] = (ui, vi, ti) 。

  • ui 是源节点( 1 到 n 的整数)
  • vi 是目标节点( 1 到 n 的整数)
  • ti 是信号从源传播到目标节点所需的时间(整数大于或等于 0 )。

还为您提供了一个整数 k ,代表我们将从中发送信号的节点。
返回所有 n 个节点接收信号所需的最短时间。如果不能使所有节点都接收到信号,则返回 -1。
在这里插入图片描述

示例1:
Input: times = [[1,2,1],[2,3,1],[1,4,4],[3,4,1]], n = 4, k = 1
Output: 3
示例2:
Input: times = [[1,2,1],[2,3,1]], n = 3, k = 2
Output: -1
解释:这里节点1不能接收到信号。

解题1: Dijkstra的算法
在这里插入图片描述
这里我们用最小堆minHeap来弹出path值(路径权重)最小的节点。

class Solution:
    def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
        edges = collections.defaultdict(list)
        for u, v, w in times:
            edges[u].append((v, w))

        minHeap = [(0, k)]
        visit = set()
        t = 0
        while minHeap:
            w1, n1 = heapq.heappop(minHeap)
            if n1 in visit:
                continue
            visit.add(n1)
            t = max(t, w1)
            for n2, w2 in edges[n1]:
                if n2 not in visit:
                    heapq.heappush(minHeap, (w1 + w2, n2))
        return t if len(visit) == n else -1

时间复杂度: O ( E l o g V ) O(ElogV) O(ElogV)
空间复杂度: O ( V + E ) O(V + E) O(V+E)
V 是顶点数,E 是边的数量。

091 Min Cost to Connect Points 最小连接点的成本

给您一个2-D整数数组 points ,其中 points[i] = [xi, yi] 。每个 points[i] 表示2-D平面上的一个明显点。
连接两个点 [xi, yi] 和 [xj, yj] 的成本是两个点之间的曼哈顿距离,即 |xi - xj| + |yi - yj| 。
返回将所有点连接在一起的最低成本,以便每对点之间完全存在一个路径。
在这里插入图片描述

示例1:
Input: points = [[0,0],[2,2],[3,3],[2,4],[4,2]]
Output: 10

解题1: Prim’s Algorithm
在这里插入图片描述
visit用来记录已经加入的节点,当它的长度等于节点个数时,就可以停止访问。这里还有一个最小堆,每个元素记录的是(cost,节点号),每次都弹出cost最小的节点。
以上面的图为例,从节点0出发,先把0加入visit和Frontier中,这时弹出最小cost–0节点。然后把该节点相邻的节点加入Frontier中,此时cost最小的是节点1,cost为4。然后我们把它从堆中弹出,并加入到visit中。

class Solution:
    def minCostConnectPoints(self, points: List[List[int]]) -> int:
        # 准备工作
        N = len(points)

        adj = { i:[] for i in range(N) } # i : list of [cost, node]

        for i in range(N):
            x1, y1 = points[i]
            for j in range(i + 1, N):
                x2, y2 = points[j]
                dist = abs(x1 - x2) + abs(y1 - y2)
                adj[i].append([dist, j])
                adj[j].append([dist, i])
        
        # 算法核心:Prim's算法
        res = 0
        visit = set()
        minH = [[0, 0]] # [cost, point], 最小堆
        while len(visit) < N:
            cost, i = heapq.heappop(minH)
            if i in visit:
                continue
            res += cost
            visit.add(i)
            # 每加入一个节点,就把该节点的邻接节点及其cost加入到堆中
            for neiCost, nei in adj[i]:
                if nei not in visit:
                    heapq.heappush(minH, [neiCost, nei])
        return res

时间复杂度: O ( n 2 l o g n ) O(n^2logn) O(n2logn)
空间复杂度: O ( n 2 ) O(n^2) O(n2)

092 Cheapest Flights Within K Stops K 站内最便宜的航班

有 n 机场,从 0 标记为 n - 1 ,通过某些航班连接。给您一个数组 flights 其中 flights[i] = [from_i, to_i, price_i] 代表从机场 from_i 到机场 to_i 的单向航班,费用 price_i
还为您提供了三个整数 src , dst 和 k 其中:

  • src 是起步机场
  • dst 是目的地机场
  • K 是您可以停留的最大次数 (不包括 src 和 dst)

返回从 src 到 dst 最便宜的价格,最多 k 停止,或者如果不可能返回 -1 。
在这里插入图片描述

示例1:
Input: n = 4, flights = [[0,1,200],[1,2,100],[1,3,300],[2,3,100]], src = 0, dst = 3, k = 1
Output: 500
从机场0到3的最佳路径以红色显示,总成本 200 + 300 = 500 。
请注意,路径 [0 -> 1 -> 2 -> 3] 仅花费400,因此便宜,但需要2个停止,这大于k。

解题1: Bellman Ford算法
在这里插入图片描述
这里我们可以使用广度优先搜索。这里还有一个额外的变量k,当我们使用广度优先遍历的时候,我们实际上遍历了k+1层。

class Solution:
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
        # 刚开始所有设置为无穷大
        prices = [float("inf")] * n
        # 这里起点的值为0
        prices[src] = 0

        for i in range(k + 1):
            temPrices = prices.copy()

            for s, d, p in flights:
                if prices[s] == float("inf"):
                    continue
                if prices[s] + p < temPrices[d]:
                    temPrices[d] = prices[s] + p
            prices = temPrices
        return -1 if prices[dst] == float("inf") else prices[dst]

时间复杂度: O ( n + ( m ∗ k ) ) O(n+(m∗k)) O(n+(mk))
空间复杂度: O ( n ) O(n) O(n)
n 是城市的数量, m 是飞行的数量, k 是停靠次数。

093 House Robber 入室抢劫者

给您一个整数阵列nums ,其中nums[i]代表i的房子所拥有的金额。房屋以直线排列,即i是(i-1)和(i+1)房子的邻居。
你正计划从房子里抢钱,但是你不能抢劫两个相邻的房子,因为如果两个相邻的房子都被闯入,保安系统会自动报警。
在不惊动警察的情况下,退回你能抢劫的最大数额的钱。

示例1:
Input: nums = [1,1,3,3]
Output: 4

解题1: 动态规划
在这里插入图片描述
这里是解决问题的决策树,但是当我们的列表很长时,这棵树会非常宽,那么我们有什么更有效的解决方法呢?
在这里插入图片描述
这里可以用动态规划把他们分解成更多个的子问题。即分解成蓝色和绿色这两种情况。当我们选择第一个位置蓝色时,我们会从接下来的[2:n]种递归。当跳过第一个位置时,则是从[1:n]中进行递归。

class Solution:
    def rob(self, nums: List[int]) -> int:
         rob1, rob2 = 0, 0

         # [rob1, rob2, n, n+1, ...]
         for n in nums:
            temp = max(n + rob1, rob2)
            rob1 = rob2
            rob2 = temp
        return rob2

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)

094 House Robber II 入室抢劫者II

你得到一个整数数组 nums,其中 nums[i] 代表第 i个房子的钱量。房子排成一个圆圈,即第一座房子和最后一栋房子是邻居。
您打算从房屋中抢劫钱财,但您不能抢劫两栋相邻的房屋,因为如果两栋相邻的房屋都被闯入,安全系统会自动提醒警方。
归还您可以在不提醒警察的情况下抢劫的最大金额。

示例1:
Input: nums = [3,4,3]
Output: 4
解释:您不能抢劫 nums[0] + nums[2] = 6,因为 nums[0] 和 nums[2] 是相邻的房屋。您可以抢劫的最大数量是 nums[1] = 4。

解题1: 动态规划

class Solution:
    def rob(self, nums: List[int]) -> int:
        return max(nums[0], self.helper(nums[1:]), self.helper(nums[:-1]))

    def helper(self, nums):
        rob1, rob2 = 0, 0
        for n in nums:
            newRob = max(rob1 + n, rob2)
            rob1 = rob2
            rob2 = newRob
        return rob2

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( 1 ) O(1) O(1)

095 Longest Palindromic Substring 最长回文子串

给定一个字符串 s ,返回最长的 s 子字符串是回文。
回文是向前和向后读取相同的字符串。
如果有多个长度相同的回文子字符串,则返回其中任意一个。

示例1:
Input: s = "ababd"
Output: "bab"
解释: “aba” 和 “bab” 都是有效答案。

示例2:
Input:  s = "abbc"
Output: "bb"

解题1: 两个指针
我们普通的方法检测是否是回文是把字符串反向遍历看是否相等,那么时间复杂度为O(n),那么整个字符串最长回文子串的时间复杂度就是O(n)*O(n²)=O(n³)。有没有更低时间复杂度的方法呢?
在这里插入图片描述
这里我们以某个字母为中心,向左向右看两边内容是否相等。我们从左到右逐个以某字母为中心。那么时间复杂度就降低为O(n)*O(n)=O(n²)。
上面是考虑回文字符串长度为奇数的情况,那么偶数实际上也是同理的。

class Solution:
    def longestPalindrome(self, s: str) -> str:
        res = ""
        resLen = 0

        for i in range(len(s)):
            # odd length 奇数长度
            l, r = i, i
            while l >= 0 and r < len(s) and s[l] == s[r]:
                if (r - l + 1) > resLen:
                    res = s[l:r+1]
                    resLen = r - l + 1
                l -= 1
                r += 1
            
            # even length 偶数长度
            l, r = i, i + 1
            while l >= 0 and r < len(s) and s[l] == s[r]:
                if (r - l + 1) > resLen:
                    res = s[l:r+1]
                    resLen = r - l + 1
                l -= 1
                r += 1
                
        return res

时间复杂度: O ( n 2 ) O(n²) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)

096 Palindromic Substrings 回文子串

给定字符串 s,返回 s 中作为回文的子字符串的数量。
回文是向前和向后读取相同的字符串。

示例1:
Input: s = "abc"
Output: 3
解释:  “a”、“b”、“c”。

示例2:
Input:  s = "aaa"
Output: 6
说明: “a”, “a”, “a”, “aa”, “aa”, “aaa”。请注意,即使字符串内容相同,不同的子字符串也会被计为不同的回文。

解题1: 两个指针

class Solution:
    def countSubstrings(self, s: str) -> int:
        res = 0

        for i in range(len(s)):
            res += self.countPali(s, i, i)
            res += self.countPali(s, i, i + 1)
        return res
    
    def countPali(self, s, l, r):
        res = 0
        while l >= 0 and r < len(s) and s[l] == s[r]:
            res += 1
            l -= 1
            r += 1
        return res

时间复杂度: O ( n 2 ) O(n²) O(n2)
空间复杂度: O ( 1 ) O(1) O(1)

097 Decode Ways 解码方式

可以使用以下映射将由大写英文字符组成的字符串编码为数字:

'A' -> "1"
'B' -> "2"
...
'Z' -> "26"

要解码消息,必须对数字进行分组,然后使用上述映射的相反方法映射回字母。可能有多种方法可以解码消息。例如, “1012” 可以映射到:

  • “JAB” 使用分组 (10 1 2)
  • “JL” 使用分组 (10 12)

分组 (1 01 2) 无效,因为 01 无法映射到字母,因为它包含前导零。
给定一个仅包含数字的字符串 s ,返回解码它的方法数。您可以假设答案适合 32 位整数。

示例1:
Input: s = "12"
Output: 2
Explanation: "12" could be decoded as "AB" (1 2) or "L" (12).

示例2:
Input: s = "01"
Output: 0
说明: “01” 无法解码,因为 “01” 无法映射到字母中。

解题1:
在这里插入图片描述
上面展示的就是用蛮力的方法

class Solution:
    def numDecodings(self, s: str) -> int:
        dp = {len(s):1}

        def dfs(i):
            if i in dp:
                return dp[i]
            if s[i] == "0":
                return 0
            
            res = dfs(i + 1)
            if (i + 1 < len(s) and (s[i] == "1" or 
                s[i] == "2" and s[i + 1] in "0123456")):
                res += dfs(i + 2)
            dp[i] = res
            return res
        return dfs(0)

时间复杂度: O ( n ) O(n) O(n)
空间复杂度: O ( n ) O(n) O(n)

098 Coin Change 硬币兑换

您将获得一个整数数组 coins ,代表不同面额的硬币(例如 1 美元、5 美元等)和一个 amount 代表目标金额的整数。
返回构成确切目标金额所需的最少硬币数。如果无法补足金额,则返回 -1 。
您可以假设每个硬币的数量是无限的。

示例1:
Input: coins = [1,5,10], amount = 12
Output: 3
解释:12 = 10 + 1 + 1。请注意,我们不必使用所有可用的硬币。

示例2:
Input: coins = [2], amount = 3
Output: -1
说明:3 的数量不能用 2 的硬币来弥补。

解题1: 自上而下的动态规划
在这里插入图片描述
如果使用贪婪算法,我们从最大的开始,但是这样我们需要三个硬币。这个题目要我们求所需硬币的最少数量,这里实际上只需要3+4两个就够了。
在这里插入图片描述
这里dp[y]=x表示总和为y需要最少x个硬币

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [amount + 1] * (amount + 1)
        dp[0] = 0
        
        for a in range(1, amount + 1):
            for c in coins:
                if a - c >= 0:
                    dp[a] = min(dp[a], 1 + dp[a - c])
        return dp[amount] if dp[amount] != amount + 1 else -1

时间复杂度: O ( n ∗ t ) O(n∗t) O(nt)
空间复杂度: O ( t ) O(t) O(t)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值