|
1 | 1 | --- |
| 2 | +title: UMPIRE 2.Add-Two-Numbers |
| 3 | +subtitle: "https://leetcode.com/problems/add-two-numbers/description/" |
| 4 | +date: 2024-03-02T15:57:00+08:00 |
| 5 | +lastmod: 2024-03-02T15:57:00+08:00 |
| 6 | +draft: false |
| 7 | +author: "Kimi.Tsai" |
| 8 | +authorLink: "https://kimi0230.github.io/" |
| 9 | +description: "UMPIRE Solution for 0002.Add-Two-Numbers" |
| 10 | +license: "" |
| 11 | +images: [] |
| 12 | + |
| 13 | +tags: |
| 14 | + - LeetCode |
| 15 | + - Go |
| 16 | + - Medium |
| 17 | + - Add Two Numbers |
| 18 | + - Linked List |
| 19 | + - Math |
| 20 | + - Recursion |
| 21 | + - Amazon |
| 22 | + - Apple |
| 23 | + - Facebook |
| 24 | + - Microsoft |
| 25 | + - Bloomberg |
| 26 | +categories: [LeetCode] |
| 27 | + |
| 28 | +featuredImage: "" |
| 29 | +featuredImagePreview: "" |
| 30 | + |
| 31 | +hiddenFromHomePage: false |
| 32 | +hiddenFromSearch: false |
| 33 | +twemoji: false |
| 34 | +lightgallery: true |
| 35 | +ruby: true |
| 36 | +fraction: true |
| 37 | +fontawesome: true |
| 38 | +linkToMarkdown: false |
| 39 | +rssFullText: false |
| 40 | + |
| 41 | +toc: |
| 42 | + enable: true |
| 43 | + auto: true |
| 44 | +code: |
| 45 | + copy: true |
| 46 | + maxShownLines: 200 |
| 47 | +math: |
| 48 | + enable: false |
| 49 | +share: |
| 50 | + enable: true |
| 51 | +comment: |
| 52 | + enable: true |
| 53 | +--- |
2 | 54 |
|
3 | 55 | # Solution - UMPIRE |
4 | 56 |
|
|
11 | 63 | - 除非是數字 0 本身,否則這兩個數字都不會有前導零。 |
12 | 64 | - 兩個鏈結串列的長度可能不同。 |
13 | 65 | - **澄清與細節**: |
14 | | - - 題目保證非空,所以不需處理空指標作為輸入的情況。 |
15 | | - - 需要處理進位(Carry)。 |
| 66 | + - 題目保證輸入串列非空,且內容皆為非負整數。 |
| 67 | + - 需要精確處理「進位(Carry)」邏輯,尤其是最後一位產生的進位。 |
16 | 68 | - **測試案例定義**: |
17 | 69 | - **Happy Path**: |
18 | 70 | - $L1: [2, 4, 3]$ (342), $L2: [5, 6, 4]$ (465) $\rightarrow$ 結果: $[7, 0, 8]$ (807) |
19 | 71 | - **Edge Cases**: |
20 | | - - **長度不同**: $L1: [9, 9, 9, 9, 9, 9, 9]$, $L2: [9, 9, 9, 9] \rightarrow$ 需要處理不同步的結束。 |
21 | | - - **最後進位**: $L1: [5]$, $L2: [5] \rightarrow$ 結果: $[0, 1]$,最後多出一個節點。 |
22 | | - - **其中一個為零**: $L1: [0]$, $L2: [7, 3] \rightarrow$ 結果: $[7, 3]$。 |
| 72 | + - **長度不同**: $L1: [9, 9, 9]$, $L2: [1] \rightarrow$ 結果: $[0, 0, 0, 1]$ (處理不同步結束與連續進位)。 |
| 73 | + - **最後進位**: $L1: [5]$, $L2: [5] \rightarrow$ 結果: $[0, 1]$ (最後迴圈結束後仍有進位)。 |
| 74 | + - **值為零**: $L1: [0]$, $L2: [0] \rightarrow$ 結果: $[0]$。 |
23 | 75 |
|
24 | 76 | ### M – Match(匹配知識) |
25 | 77 | - **主要演算法/資料結構**:鏈結串列遍歷(Linked List Traversal)與基礎算術(Elementary Math)。 |
26 | 78 | - **為什麼適合**: |
27 | | - - 因為數字已經是逆序儲存,這非常符合我們手寫加法的習慣(從個位數開始加),我們只需要同時遍歷兩個串列即可。 |
| 79 | + - 數字逆序儲存的特性與我們手寫加法「從右往左」的順序完全一致,只需單向遍歷即可。 |
28 | 80 | - **為什麼其他解法不理想**: |
29 | | - - 若先將鏈結串列轉為大整數(BigInt)再相加後轉回串列,雖然可行,但會消耗額外的空間與轉換時間,且無法處理超過 64 位元整數範圍的情況(若題目沒有明確限制範圍)。直接在鏈結串列上操作是最直觀且有效率的。 |
| 81 | + - 轉換成整數再相加會面臨整數溢位(Integer Overflow)的問題,且轉換過程會產生額外的時間與空間開銷。直接在鏈結串列上逐位計算是最符合題意的最優解。 |
30 | 82 |
|
31 | 83 | ### P – Plan(制定計畫) |
32 | 84 | - **解題流程**: |
33 | | - 1. 初始化一個 `dummyHead` 節點來作為結果串列的起始,以及一個指標 `curr` 指向它。 |
34 | | - 2. 初始化 `carry` 為 0。 |
35 | | - 3. 同時遍歷 `l1` 和 `l2`,只要 `l1 != nil` 或 `l2 != nil` 或 `carry != 0` 就繼續迴圈: |
36 | | - - 取得 `l1` 的值(若已結束則為 0)。 |
37 | | - - 取得 `l2` 的值(若已結束則為 0)。 |
38 | | - - 計算 `sum = val1 + val2 + carry`。 |
39 | | - - 更新 `carry = sum / 10`。 |
40 | | - - 創建新節點其值為 `sum % 10`,掛在 `curr.Next`。 |
41 | | - - 將 `curr`、`l1`、`l2` 分別移至下一個位置。 |
| 85 | + 1. 使用 `dummyHead` 簡化首個節點的處理,並用 `curr` 指標追蹤結果串列的尾部。 |
| 86 | + 2. 初始化進位變數 `carry = 0`。 |
| 87 | + 3. 遍歷條件:只要 `l1` 沒走完 OR `l2` 沒走完 OR 還有進位 `carry != 0`: |
| 88 | + - 從 `l1` 取值(若為 nil 則補 0)。 |
| 89 | + - 從 `l2` 取值(若為 nil 則補 0)。 |
| 90 | + - `sum = val1 + val2 + carry`。 |
| 91 | + - `carry = sum / 10`。 |
| 92 | + - 建立值為 `sum % 10` 的新節點,接在 `curr.Next`。 |
| 93 | + - 移動 `curr` 以及 `l1` / `l2` 指標。 |
42 | 94 | 4. 回傳 `dummyHead.Next`。 |
43 | 95 | - **資料流與狀態變化**: |
44 | | - - 每次疊代都會處理一位數。 |
45 | | - - `carry` 狀態會在下一次疊代中被累加。 |
| 96 | + - `carry` 狀態跨越每一次疊代傳遞。 |
| 97 | + - 透過 `l1` 與 `l2` 的同步移動確保位數對齊。 |
46 | 98 | - **預防 Bug**: |
47 | | - - 注意迴圈結束條件,必須包含 `carry != 0`,否則最後一位進位會被遺漏(例如 5+5=10)。 |
48 | | - - 檢查指標是否為 `nil` 才能存取 `Val`。 |
| 99 | + - 確保 `while` 迴圈結束後,如果 `carry` 仍大於 0,必須補上最後一個節點。 |
| 100 | + - 在存取 `l1.Val` 之前務必檢查 `l1 != nil`。 |
49 | 101 |
|
50 | 102 | ### I – Implement(實際實作,Golang) |
51 | 103 | ```go |
@@ -84,43 +136,43 @@ func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode { |
84 | 136 |
|
85 | 137 | ### R – Review(檢查與回顧) |
86 | 138 | - **Dry Run**: |
87 | | - - 輸入:$L1: [2, 4, 3], L2: [5, 6, 4]$ |
| 139 | + - 輸入:$L1: [2, 4], L2: [5, 6, 4]$ |
88 | 140 | - 1st: $2+5+0 = 7, carry=0, Result: [7]$ |
89 | 141 | - 2nd: $4+6+0 = 10, carry=1, Result: [7, 0]$ |
90 | | - - 3rd: $3+4+1 = 8, carry=0, Result: [7, 0, 8]$ |
91 | | - - 結束。回傳 $[7, 0, 8]$。 |
92 | | -- **關鍵狀態轉移**:`carry` 正確地從十位數轉移到下一位的個位數。 |
93 | | -- **邊界確認**:如果輸入是 $L1:[9], L2:[1]$,迴圈會跑兩次,第二次處理 `carry=1`,產出 $[0, 1]$,正確。 |
| 142 | + - 3rd: $0+4+1 = 5, carry=0, Result: [7, 0, 5]$ |
| 143 | + - 回傳 $[7, 0, 5]$,正確($42 + 465 = 507$)。 |
| 144 | +- **關鍵狀態轉移**:進位正確地在疊代間傳遞。 |
| 145 | +- **邊界確認**:單節點 $5+5$ 會產生兩個節點的結果 $[0, 1]$,邏輯正確。 |
94 | 146 |
|
95 | 147 | ### E – Evaluate(總結與評估) |
96 | | -- **時間複雜度**: $O(\max(M, N))$,其中 $M$ 和 $N$ 分別為兩個串列的長度。我們只需遍歷最長的那一個一次。 |
97 | | -- **空間複雜度**: $O(\max(M, N))$,回傳的結果串列長度最多為 $\max(M, N) + 1$。若不計入回傳空間,則為 $O(1)$。 |
98 | | -- **權衡與優化**:這個方法已經是時間與空間的最優解。唯一的優化可能是原位(In-place)修改,但因為相加後長度可能增加,且通常不建議修改輸入參數,所以使用新串列是標準做法。 |
| 148 | +- **時間複雜度**: $O(\max(M, N))$,其中 $M, N$ 為兩串列長度。我們只遍歷了最長串列一次。 |
| 149 | +- **空間複雜度**: $O(\max(M, N))$,用於儲存結果串列(若不計入回傳值則為 $O(1)$)。 |
| 150 | +- **權衡與優化**:本解法已達最優。若要極致優化空間,可考慮原位修改(In-place),但會破壞輸入資料且程式碼複雜度會大幅提升,通常不建議這樣做。 |
99 | 151 |
|
100 | 152 | --- |
101 | 153 |
|
102 | 154 | ## Output 2: 面試官口語回答腳本(精簡可直接說) |
103 | 155 |
|
104 | 156 | ### 1️⃣ 開場:題目理解 |
105 | | -這題要求我們將兩個以逆序鏈結串列表示的整數相加,並回傳同樣是逆序的結果串列。核心在於如何處理兩串列長度不等的情況,以及數值相加後的進位處理。 |
| 157 | +這題要求我們處理兩個逆序排列的鏈結串列,將它們代表的整數相加。主要的挑戰在於處理進位以及當兩個串列長度不一致時的對齊問題。 |
106 | 158 |
|
107 | 159 | ### 2️⃣ 解法選擇說明 |
108 | | -我會採用**同時遍歷(Simultaneous Traversal)**的方法。因為題目給的數字已經是從個位數開始的逆序排列,這正好符合手算加法從右到左的邏輯。這種做法的時間複雜度是線性 $O(\max(M, N))$,是最直觀且高效的解法。 |
| 160 | +我選擇採用**線性遍歷(Simultaneous Traversal)**配合**基礎進位邏輯**。因為串列已經是逆序排列(低位在首),這正好符合我們手算加法的順序,直接一邊走一邊加是最有效率的,時間複雜度是 $O(\max(M, N))$。 |
109 | 161 |
|
110 | 162 | ### 3️⃣ 解題策略概覽 |
111 | | -我會建立一個 Dummy Head 來簡化鏈結串列的操作。接著使用一個 `while` 迴圈同時遍歷兩個串列,並維護一個 `carry` 變數來存儲進位值。在每一次疊代中,我把兩個節點的值與當前的進位相加,取餘數作為新節點的值,取商數作為下一次的進位。這個迴圈會持續運行,直到兩個串列都走完「且」進位值為 0。 |
| 163 | +我會建立一個 **Dummy Head** 來方便管理結果串列的成長。在迴圈中,我會同時檢查兩個指標,只要其中一個還有數字或者還有進位,我就把值加總起來,存入新節點,並把產生的進位傳給下一輪。 |
112 | 164 |
|
113 | 165 | ### 4️⃣ 寫程式時會補充的關鍵說明 |
114 | | -* **Dummy Head 的使用**:這可以讓我們在建立結果串列時,不需要特別判斷第一個節點是否為空,代碼會更簡潔。 |
115 | | -* **防呆判斷**:在取值時,一定要先檢查指標是否為 `nil`,如果其中一個串列比較短,我們就補 0 進行運算。 |
116 | | -* **最後進位處理**:迴圈的條件必須包含 `carry != 0`,確保像是 $99 + 1$ 這種最後產生的進位能被正確補在串列末端。 |
| 166 | +* **Dummy Head 技巧**:使用虛擬頭節點可以讓程式碼更簡潔,避免在迴圈內一直判斷 `head` 是否為空的邏輯。 |
| 167 | +* **靈活處理不等長**:當其中一個串列結束時,我會將其值視為 $0$,這樣可以用同一套邏輯處理完所有的位數。 |
| 168 | +* **強健的結束條件**:我的迴圈條件會包含 `carry != 0`,這能自動處理像是 $5+5$ 這種最後多出一位進位的情況。 |
117 | 169 |
|
118 | 170 | ### 5️⃣ 快速 Dry Run 說明 |
119 | | -以 $[2, 4, 3]$ 加 $[5, 6, 4]$ 為例: |
120 | | -第一位 $2+5=7$,無進位。 |
121 | | -第二位 $4+6=10$,寫 $0$ 進位 $1$。 |
122 | | -第三位 $3+4$ 再加上剛才的進位 $1$ 等於 $8$。 |
123 | | -最後結果就是 $[7, 0, 8]$。 |
| 171 | +假設 $L1=[2, 4]$,$L2=[5, 6, 4]$。 |
| 172 | +第一位相加 $2+5=7$,無進位。 |
| 173 | +第二位相加 $4+6=10$,結果寫 $0$,進位 $1$。 |
| 174 | +最後 $L1$ 走完了,剩 $L2$ 的 $4$ 加上剛才的進位 $1$ 等於 $5$。 |
| 175 | +最終結果就是 $[7, 0, 5]$。 |
124 | 176 |
|
125 | 177 | ### 6️⃣ 收尾總結 |
126 | | -這套演算法的**時間複雜度是 $O(\max(M, N))$**,**空間複雜度也是 $O(\max(M, N))$**(用於儲存結果)。這種解法穩定且能處理極大的數值,因為它完全避開了整數溢出的限制。 |
| 178 | +這套演算法在**時間與空間複雜度上都是線性 $O(\max(M, N))$**。它能有效處理任意長度的數字,且不受程式語言整數型態大小的限制。 |
0 commit comments