時間複雜度:O(n2),其中 n 為二叉樹的節點數,每次查找 root 在 inorder 中的位置需要 O(n) 的時間。
空間複雜度:O(n2),最壞情況下,每次遞迴都需要 O(n) 的空間。
1 2 3 4 5 6 7 8 9 10
classSolution: defbuildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]: n = len(postorder) if n == 0: returnNone root = TreeNode(postorder[-1]) idx = inorder.index(postorder[-1]) # root index in inorder, also the size of left subtree root.left = self.buildTree(inorder[:idx], postorder[:idx]) root.right = self.buildTree(inorder[idx+1:], postorder[idx:n-1]) return root
時間複雜度:O(n2),其中 n 為二叉樹的節點數,每次查找 left 在 postorder 中的位置需要 O(n) 的時間。
空間複雜度:O(n2),最壞情況下,每次遞迴都需要 O(n) 的空間。
1 2 3 4 5 6 7 8 9 10 11 12
classSolution: defconstructFromPrePost(self, preorder: List[int], postorder: List[int]) -> Optional[TreeNode]: n = len(postorder) if n == 0: returnNone root = TreeNode(preorder[0]) if n == 1: return root idx = postorder.index(preorder[1]) # let preorder[1] be the root of left subtree, then find the index of preorder[1] in postorder root.left = self.constructFromPrePost(preorder[1:idx+2], postorder[:idx+1]) # size of left subtree is idx+1 root.right = self.constructFromPrePost(preorder[idx+2:], postorder[idx+1:n-1]) return root
classSolution: defkthLargestLevelSum(self, root: Optional[TreeNode], k: int) -> int: hp = [] # min heap, size k q = deque([root]) while q: cur = 0 for _ inrange(len(q)): node = q.popleft() cur += node.val if node.left: q.append(node.left) if node.right: q.append(node.right) iflen(hp) < k: heappush(hp, cur) else: if cur > hp[0]: heapreplace(hp, cur) # first pop then push return hp[0] iflen(hp) == k else -1
q = deque([(src, 0, 0)]) # BFS while q: u, d, s = q.popleft() if s > k: # Pruning break for v in adj[u]: if dist[v] > d + g[u][v]: dist[v] = d + g[u][v] q.append((v, dist[v], s+1)) return dist[dst] if dist[dst] != float('inf') else -1
for t inrange(1, k + 2): for u, v, w in flights: dp[v][t] = min(dp[v][t], dp[u][t-1] + w) ans = min(dp[dst][t] for t inrange(1, k + 2)) return -1if ans == float("inf") else ans
方法三:Bellman-Ford Algorithm
Bellman-Ford 演算法是一種 單源最短路算法 ,可以處理有負權邊的圖。
每次迭代,我們都對所有的航班進行 鬆弛(relaxation) 操作,即 dist[v]=min(dist[v],dist[u]+w) ,經過 k+1 次迭代後,可得到從 src 到 v 經過 k+1 步 (即 k 次中轉)的最小花費,最後返回 dist[dst] 即為答案。
時間複雜度:O((n+m)⋅k),其中 n 為城市數,m 為航班數。總共進行 k+1 次迭代,每次迭代需要 O(n) 的時間備份距離,且每次迭代需要考慮 m 條航班。
空間複雜度:O(n2)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution: deffindCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int: g = [[float("inf")] * n for _ inrange(n)] for u, v, w in flights: g[u][v] = min(g[u][v], w)
dist = [float('inf')] * n dist[src] = 0
while k >= 0: # k+1 iterations clone = dist[:] for i inrange(n): for j inrange(n): if clone[i] + g[i][j] < dist[j]: dist[j] = clone[i] + g[i][j] k -= 1 return -1if dist[dst] == float("inf") else dist[dst]
while k >= 0: clone = dist[:] for u, v, w in flights: if clone[u] + w < dist[v]: dist[v] = clone[u] + w k -= 1 return -1if dist[dst] == float("inf") else dist[dst]
ans = [] for q in queries: idx1 = bisect_left(nodes, q) # Find the first element that is not less than q (>=) idx2 = bisect_right(nodes, q) # Find the first element that is greater than q (>) if idx1 != idx2: # q is in the tree ans.append([q, q]) else: if idx1 == 0: ans.append([-1, nodes[idx1]]) elif idx1 == len(nodes): ans.append([nodes[idx1-1], -1]) else: ans.append([nodes[idx1-1], nodes[idx1]]) return ans
meetings.sort(key=lambda x: x[2]) # sort by time union(0, firstPerson) # firstPerson 知道秘密
time = 0# 同一時間的會議,在每個連通分量中,需要有人知道秘密才能傳遞 people = set() # 這個時間點進行會議的人 for x, y, t in meetings: if t != time: # 進入下一個時間點,檢查前一個時間點是否有人知道秘密 time = t for p in people: ifnot isSame(firstPerson, p): isolate(p) people = set() union(x, y) people.add(x) people.add(y)
ans = [] for i inrange(n): if isSame(i, firstPerson): ans.append(i) return ans
# Initialize the list of prime factors for each number MAXN = 10 ** 5 + 5 fac = defaultdict(list) for i inrange(2, MAXN): ifnot fac[i]: # i is prime for j inrange(i, MAXN, i): # mark all multiples of i fac[j].append(i)
若 p 和 q 皆非空節點,且其值相等,則進行遞迴判斷其左右子樹是否相同;若 p 和 q 皆為空節點,則返回 true;若 p 和 q 一個為空節點,另一個不為空節點,則返回 false。後兩者可以在確認 p 和 q 皆不為非空節點後,合併成 p==q。
時間複雜度:O(n),其中 n 為樹的節點數。
空間複雜度:O(h),其中 h 為樹的高度,即遞迴使用的堆疊(Stack)空間。
1 2 3 4 5
classSolution: defisSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool: if p and q: return p.val == q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) return p == q
和 2709. Greatest Common Divisor Traversal(2024-02-25)中所使用的方法類似,但本題中我們只關注是否為質數,而非對所有數字進行質因數分解,因此我們只要考慮至 floor(MAXN) 即可;且在標記質數時,可以直接從 i∗i 開始標記,因為 i 的所有小於 i 的倍數已經被標記過了。
需注意路徑 (a,b) 和路徑 (b,a) 被視為 相同 的路徑,只會被計算 一次。我們可以紀錄已經被考慮過的非質數節點數量 cur ,並再考慮新的大小為 size 的連通分量時,將 cur 乘上 size 後加入答案中,再把 cur 加上 size。考慮完所有連通分量後,等同考慮完兩側皆為非質數節點的情況,而此時 cur 即為所有非質數節點的數量,故再加上 cur 本身,也就是以質數節點作為起點的有效路徑,即可得到所有經過該質數的有效路徑數量。
舉例來說,假設我們枚舉了質數 x 作為根節點,並且與 x 相鄰的非質數節點的連通分量分別為 2,3,4,則有效路徑數目為 (0)∗2+(2)∗3+(2+3)∗4+(2+3+4)。
至於連通分量(connected component) 的大小,可以使用Disjoint Set Union(DSU) 或深度優先搜索(DFS) 來計算,後者在速度上會快一些。
方法一:質數判定 + Disjoint Set Union(DSU) + 乘法原理
對於每個非質數節點 x,我們可以將其與與其相鄰的非質數節點 y 進行 聯集(Union) 操作,操作時以 x 的根節點 fx 作為根節點,並將其連通分量的大小相加,即可求得連通分量的大小。
# Initialize the list of prime numbers for each number MAXN = 10 ** 5 + 5 isPrime = [True] * MAXN isPrime[0] = isPrime[1] = False for i inrange(2, math.isqrt(MAXN) + 1): if isPrime[i]: # i is prime for j inrange(i*i, MAXN, i): # mark all multiples of i isPrime[j] = False
classSolution: defcountPaths(self, n: int, edges: List[List[int]]) -> int: g = [[] for _ inrange(n + 1)] for u, v in edges: g[u].append(v) g[v].append(u)
# 用 Disjoint Set Union(DSU) 計算所有非質數節點的連通分量大小 pa = list(range(n + 1)) # parent
size = [1] * (n + 1) # size of connected component for x inrange(1, n + 1): if isPrime[x]: continue fx = find(x) for y in g[x]: if isPrime[y]: continue fy = find(y) if fy == fx: continue size[fx] += size[fy] # 以 x 為根節點合併 pa[fy] = fx ans = 0 for x inrange(1, n+1): # 枚舉所有質數節點 x ifnot isPrime[x]: continue cur = 0# 起點數量 for y in g[x]: # 枚舉與 x 相鄰的非質數節點 y if isPrime[y]: continue fy = find(y) ans += cur * size[fy] # 乘法原理:起點數量 * 終點數量 cur += size[fy] ans += cur # 以 x 為終點 return ans
# Initialize the list of prime numbers for each number MAXN = 10 ** 5 + 5 isPrime = [True] * MAXN isPrime[0] = isPrime[1] = False for i inrange(2, math.isqrt(MAXN) + 1): if isPrime[i]: # i is prime for j inrange(i*i, MAXN, i): # mark all multiples of i isPrime[j] = False
classSolution: defcountPaths(self, n: int, edges: List[List[int]]) -> int: g = [[] for _ inrange(n + 1)] for u, v in edges: g[u].append(v) g[v].append(u)
defdfs(x: int, pa: int) -> None: nodes.append(x) for y in g[x]: if y == pa or isPrime[y]: continue dfs(y, x)
ans = 0 size = [0] * (n + 1) # size of connected component for x inrange(1, n+1): # 枚舉所有質數節點 x ifnot isPrime[x]: continue cur = 0# 起點數量 for y in g[x]: # 枚舉與 x 相鄰的非質數節點 y if isPrime[y]: continue if size[y] == 0: # y 所在的連通分量尚未被計算過 nodes = [] dfs(y, -1) for z in nodes: # 將該連通分量的所有非質數節點的 size 設為該連通分量的大小 size[z] = len(nodes) ans += cur * size[y] # 乘法原理:起點數量 * 終點數量 cur += size[y] ans += cur # 以 x 為終點 return ans
classSolution: defrootCount(self, edges: List[List[int]], guesses: List[List[int]], k: int) -> int: n = len(edges) + 1 g = [[] for _ inrange(n)] for u, v in edges: g[u].append(v) g[v].append(u) st = set([(u, v) for u, v in guesses]) @cache defdfs(u: int, fa: int) -> int: res = 0 for v in g[u]: if v == fa: continue if (u, v) in st: res += 1# 對於這條邊的父子關係,猜對了 res += dfs(v, u) return res
ans = 0 for i inrange(n): ans += (dfs(i, -1) >= k) return ans
classSolution: defrootCount(self, edges: List[List[int]], guesses: List[List[int]], k: int) -> int: n = len(edges) + 1 g = [[] for _ inrange(n)] for u, v in edges: g[u].append(v) g[v].append(u) st = set([(u, v) for u, v in guesses]) # @cache # 若用換根DP,只會執行一次DFS,因此不需要memoization defdfs(u: int, fa: int) -> int: res = 0 for v in g[u]: if v == fa: continue if (u, v) in st: res += 1# 對於這條邊的父子關係,猜對了 res += dfs(v, u) return res
defreroot(x: int, fa: int, cnt: int) -> int: # 再執行一次DFS來做換根 ans = (cnt >= k) # 此時 cnt 為以 x 為根時的猜對數量,若滿足條件則答案+1 for y in g[x]: if y == fa: continue ans += reroot(y, x, cnt - ((x, y) in st) + ((y, x) in st)) # 改以 y 為根時,猜對數量的變化 return ans cnt0 = dfs(0, -1) # 以 0 為根時的猜對數量 return reroot(0, -1, cnt0)