文章目录
- 089 Redundant Connection 冗余连接
- 090 Network Delay Time 网络延迟时间
- 091 Min Cost to Connect Points 最小连接点的成本
- 092 Cheapest Flights Within K Stops K 站内最便宜的航班
- 093 House Robber 入室抢劫者
- 094 House Robber II 入室抢劫者II
- 095 Longest Palindromic Substring 最长回文子串
- 096 Palindromic Substrings 回文子串
- 097 Decode Ways 解码方式
- 098 Coin Change 硬币兑换
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+(m∗k))
空间复杂度:
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(n∗t)
空间复杂度:
O
(
t
)
O(t)
O(t)