|
| 1 | +# 1462. Course Schedule IV |
| 2 | + |
| 3 | +There are a total of `numCourses` courses you have to take, labeled from `0` to `numCourses - 1`. |
| 4 | +You are given an array `prerequisites` where `prerequisites[i] = [a_i, b_i]` indicates that |
| 5 | +you must take course `a_i` first if you want to take course `b_i`. |
| 6 | + |
| 7 | +For example, the pair `[0, 1]` indicates that you have to take course `0` before you can take course `1`. |
| 8 | +Prerequisites can also be indirect. |
| 9 | +If course `a` is a prerequisite of course `b`, |
| 10 | +and course `b` is a prerequisite of course `c`, then course `a` is a prerequisite of course `c`. |
| 11 | + |
| 12 | +You are also given an array `queries` where `queries[j] = [uj, vj]`. |
| 13 | +For the `j_th` query, you should answer whether course `u_j` is a prerequisite of course `v_j` or not. |
| 14 | + |
| 15 | +Return a boolean array answer, where `answer[j]` is the answer to the `j_th` query. |
| 16 | + |
| 17 | +## 基礎思路 |
| 18 | +這題需要構建關聯表,然後查詢是否有關聯。我們可以把這題轉化成一個有向圖,然後查詢是否有路徑。 |
| 19 | +在這題我將採取深度優先搜索的方式來解決這個問題。 |
| 20 | + |
| 21 | +## 解題步驟 |
| 22 | + |
| 23 | +### Step 1: 初始化圖,並將直接關聯的節點加入圖中 |
| 24 | + |
| 25 | +```typescript |
| 26 | +// 圖會是一個二維數組,用來記錄每個節點的關聯性 |
| 27 | +const graph: number[][] = Array.from({ length: numCourses }, () => []); |
| 28 | + |
| 29 | +// 先把直接關聯的節點加入圖中 |
| 30 | +for (const [u, v] of prerequisites) { |
| 31 | + graph[u].push(v); |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +### Step 2: 深度優先搜索 |
| 36 | + |
| 37 | +```typescript |
| 38 | +// 定義可到達的節點 |
| 39 | +const reachable = Array.from({ length: numCourses }, () => new Set<number>()); |
| 40 | + |
| 41 | +// 深度優先搜索 |
| 42 | +const dfs = (node: number) => { |
| 43 | + for (const neighbor of graph[node]) { |
| 44 | + // 如果已經建構過這個節點的關聯性,則跳過 |
| 45 | + if (reachable[node].has(neighbor)) { |
| 46 | + continue; |
| 47 | + } |
| 48 | + |
| 49 | + // 新增節點到可到達的節點中 |
| 50 | + reachable[node].add(neighbor); |
| 51 | + |
| 52 | + // 遞迴搜索其鄰居 |
| 53 | + dfs(neighbor); |
| 54 | + |
| 55 | + // 新增鄰居的可到達節點到當前節點的可到達節點中 |
| 56 | + for (const n of reachable[neighbor]) { |
| 57 | + reachable[node].add(n); |
| 58 | + } |
| 59 | + } |
| 60 | +}; |
| 61 | +``` |
| 62 | + |
| 63 | +### Step 3: 遍歷所有節點,並進行深度優先搜索 |
| 64 | + |
| 65 | +```typescript |
| 66 | +for (let i = 0; i < numCourses; i++) { |
| 67 | + dfs(i); |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +### Step 4: 查詢是否有關聯 |
| 72 | + |
| 73 | +```typescript |
| 74 | +return queries.map(([u, v]) => reachable[u].has(v)); |
| 75 | +``` |
| 76 | + |
| 77 | +## 時間複雜度 |
| 78 | +- **建圖成本:** |
| 79 | + - 用鄰接表建圖的時間複雜度是 $O(m)$,這是所有預修課程所構成的邊的數量。 |
| 80 | + |
| 81 | +- **DFS 及可達性集合 (Union 操作):** |
| 82 | + - **DFS 遍歷的部分:** 每次從一個節點開始進行 DFS,在遍歷的過程中,每條邊最多被訪問一次,所以這部分的遍歷成本為 $O(m)$。 |
| 83 | + - **更新集合的部分:** 每次進行集合合併操作時,可能需要將 $reachable[neighbor]$ 的所有節點合併到 $reachable[node]$。在最壞情況下,這個集合可能包含最多 $O(n)$ 節點。 |
| 84 | + 因此,每次合併的成本是 $O(n)$,而且每條邊的操作成本因此變為 $O(n)$。 |
| 85 | + DFS 和集合合併操作的總成本是: |
| 86 | + $$ |
| 87 | + O(n \cdot (n + m)) = O(n^2 + n \cdot m) |
| 88 | + $$ |
| 89 | + 因為我們需要對每個節點執行 DFS,總成本乘以 $n$,得到: |
| 90 | + $$ |
| 91 | + O(n \cdot (n^2 + n \cdot m)) = O(n^3 + n^2 \cdot m) |
| 92 | + $$ |
| 93 | + |
| 94 | +- **查詢成本:** |
| 95 | + 每次查詢 $reachable[u]$ 是否包含 $v$ 是 $O(1)$,對於 $q$ 個查詢,總成本是 $O(q)$。 |
| 96 | + |
| 97 | +> $O(n^3 + n^2 \cdot m + m + q) \approx O(n^3 + n^2 \cdot m)$ |
| 98 | +
|
| 99 | +## 空間複雜度 |
| 100 | + |
| 101 | +- **鄰接表儲存空間:** $O(n + m)$。 |
| 102 | +- **Reachable 集合儲存空間:** $O(n^2)$,因為每個節點最多需要儲存 $n$ 個可達節點。 |
| 103 | +- **遞歸堆疊:** 最深可能達到 $O(n)$。 |
| 104 | + |
| 105 | +> $O(n^2 + m)$ |
0 commit comments