|
1 | 1 | --- |
2 | | -title: UMPIRE 0001.Two-Sum |
| 2 | +title: UMPIRE 0001.Two Sum |
3 | 3 | tags: |
4 | 4 | - Easy |
5 | 5 | author: Kimi Tsai <kimi0230@gmail.com> |
6 | | -description: UMPIRE Solution for Two Sum problem |
| 6 | +description: UMPIRE Solution for 0001.Two Sum |
7 | 7 | math: |
8 | 8 | enable: true |
9 | 9 | --- |
10 | 10 |
|
11 | | -# UMPIRE 0001.Two-Sum |
| 11 | +# UMPIRE 0001.Two Sum |
12 | 12 |
|
13 | 13 | ## Output 1: UMPIRE 解題(完整思考版) |
14 | 14 |
|
15 | 15 | ### U – Understand(理解題目) |
16 | | -- **題目描述**:給定一個整數陣列 `nums` 和一個目標值 `target`,請在該陣列中找出和為目標值的那兩個整數,並返回它們的陣列下標。 |
| 16 | +- **題目描述**:在一個整數陣列 `nums` 中,找出兩個數字,其總和等於給定的目標值 `target`。返回這兩個數字的陣列索引。 |
17 | 17 | - **關鍵限制**: |
18 | | - - 每種輸入只會對應一個答案。 |
19 | | - - 不能重複使用陣列中同樣的元素(即同一個 index 不能用兩次)。 |
20 | | - - 答案可以按任意順序返回。 |
21 | | -- **潛在陷阱**: |
22 | | - - 陣列中可能包含負數。 |
23 | | - - 陣列可能未排序(此題未註明排序,不能直接用雙指針而不先排序)。 |
| 18 | + - 每種輸入正好只有一個解。 |
| 19 | + - 同一個元素不能使用兩次(索引不能重複)。 |
| 20 | + - 答案順序不限。 |
24 | 21 | - **Happy Path**: |
25 | | - - `nums = [2, 7, 11, 15], target = 9` -> 返回 `[0, 1]`。 |
| 22 | + - `nums = [2, 7, 11, 15], target = 9` -> 返回 `[0, 1]` ($2 + 7 = 9$)。 |
26 | 23 | - **Edge Cases**: |
27 | | - - `nums = [3, 3], target = 6` -> 返回 `[0, 1]`(數值相同但 index 不同)。 |
28 | | - - `nums = [3, 2, 4], target = 6` -> 返回 `[1, 2]`(目標值由非連續元素組成)。 |
| 24 | + - 補數在當前數字之後:`nums = [3, 2, 4], target = 6` -> 返回 `[1, 2]` ($2 + 4 = 6$)。 |
| 25 | + - 陣列包含負數:`nums = [-1, -2, -3, -4, -5], target = -8` -> 返回 `[2, 4]` ($-3 + -5 = -8$)。 |
29 | 26 |
|
30 | 27 | ### M – Match(匹配知識) |
31 | | -- **主要模式**:**Hash Map (Hash Table)**。 |
| 28 | +- **主要模式**:**Hash Map (雜湊表)**。 |
32 | 29 | - **為什麼適合**: |
33 | | - - 我們需要快速尋找「當前數值的互補值」(即 `target - nums[i]`)是否已經在之前出現過。Hash Map 的查找時間複雜度為 $O(1)$,比起線性尋找的 $O(n)$ 快得多。 |
| 30 | + - 為了將搜尋時間從 $O(n)$ 降到 $O(1)$,我們可以使用 Hash Map 存儲已經遍歷過的數字及其對應的索引。這使得我們對於每個數字 `x`,都能在常數時間內判斷 `target - x` 是否已經出現過。 |
34 | 31 | - **其他方案**: |
35 | | - - **暴力解 (Brute Force)**:雙層迴圈遍歷所有組合。時間複雜度 $O(n^2)$,效率較低。 |
36 | | - - **排序 + 雙指針**:先排序再從兩端逼近。時間複雜度 $O(n \log n)$(受限於排序),雖然空間複雜度可降至 $O(1)$,但若題目要求返回原始下標,排序會打亂位置,需額外處理,不如 Hash Map 直觀且快。 |
| 32 | + - **暴力解 (Brute Force)**:使用雙層迴圈檢查所有可能的配對。時間複雜度為 $O(n^2)$,在資料量大時效率極低。 |
| 33 | + - **排序 + 雙指針**:先對陣列排序,然後使用左右指針逼近目標值。時間複雜度為 $O(n \log n)$。雖然空間複雜度優於 Hash Map,但排序會改變索引位置,需要額外處理。 |
37 | 34 |
|
38 | 35 | ### P – Plan(制定計畫) |
39 | | -1. 初始化一個空的分佈式雜湊表 (Map),用來儲存 `數值 : 下標` 的映射。 |
40 | | -2. 遍歷陣列 `nums`,對於每個元素 `v` 和其索引 `i`: |
41 | | - - 計算需要的互補值 `complement = target - v`。 |
42 | | - - 在 Map 中檢查 `complement` 是否已存在: |
43 | | - - 如果**存在**:表示找到答案,返回 `[Map[complement], i]`。 |
44 | | - - 如果**不存在**:將當前數值與索引存入 Map `Map[v] = i`。 |
45 | | -3. 如果遍歷結束仍未找到(根據題目假設這不會發生),返回空或預設值。 |
46 | | -- **避免的 Bug**:先檢查再存入,可以自然避免「重複使用同一個元素」的問題(因為 Map 裡只會有之前處理過的元素)。 |
| 36 | +1. 建立一個空的 Map `m`,其中鍵 (key) 為數字的值,值 (value) 為數字在陣列中的索引。 |
| 37 | +2. 遍歷陣列 `nums` 中的每一個元素 `v` 與其索引 `i`: |
| 38 | + - 計算所需的補數 `complement = target - v`。 |
| 39 | + - 檢查 `complement` 是否已經存在於 `m` 中。 |
| 40 | + - 如果**存在**:表示我們找到了那一對數字,返回 `[m[complement], i]`。 |
| 41 | + - 如果**不存在**:將當前數字 `v` 與索引 `i` 存入 `m` 中,以便後續元素查找。 |
| 42 | +3. 如果遍歷完整個陣列仍未找到(根據題目假設這不會發生),則返回預設值。 |
| 43 | + |
| 44 | +**Mermaid Diagram**: |
| 45 | +```mermaid |
| 46 | +graph TD |
| 47 | + A[開始遍歷 nums] --> B{計算 complement = target - v} |
| 48 | + B --> C{"Map 中是否有 complement?"} |
| 49 | + C -- 有 --> D["返回 [Map[complement], 當前索引 i]"] |
| 50 | + C -- 無 --> E["將 {v: i} 存入 Map"] |
| 51 | + E --> F{是否還有元素?} |
| 52 | + F -- 是 --> A |
| 53 | + F -- 否 --> G[結束] |
| 54 | +``` |
47 | 55 |
|
48 | 56 | ### I – Implement(實際實作,Golang) |
49 | 57 | ```go |
50 | 58 | func twoSum(nums []int, target int) []int { |
51 | | - // 建立一個 map,key 是數值,value 是對應的索引 |
52 | | - m := make(map[int]int) |
53 | | - |
54 | | - for i, v := range nums { |
55 | | - complement := target - v |
56 | | - // 檢查互補值是否已經在 map 中 |
57 | | - if idx, ok := m[complement]; ok { |
58 | | - return []int{idx, i} |
59 | | - } |
60 | | - // 如果沒找到,把當前數值存入 map |
61 | | - m[v] = i |
62 | | - } |
63 | | - |
64 | | - return []int{} |
| 59 | + // 初始化 map,用於存儲:數值 -> 索引 |
| 60 | + m := make(map[int]int) |
| 61 | + |
| 62 | + for i, v := range nums { |
| 63 | + complement := target - v |
| 64 | + // 檢查補數是否已在之前遍歷的元素中 |
| 65 | + if idx, ok := m[complement]; ok { |
| 66 | + return []int{idx, i} |
| 67 | + } |
| 68 | + // 若沒找到,將當前元素存入 map |
| 69 | + m[v] = i |
| 70 | + } |
| 71 | + |
| 72 | + return []int{} |
65 | 73 | } |
66 | 74 | ``` |
67 | 75 |
|
68 | 76 | ### R – Review(檢查與回顧) |
69 | | -- **Dry Run**:以 `nums = [3, 2, 4], target = 6` 為例: |
70 | | - 1. `i=0, v=3`:`complement=3`。Map 為空,不匹配。Map 存入 `{3: 0}`。 |
71 | | - 2. `i=1, v=2`:`complement=4`。Map 只有 `{3: 0}`,不匹配。Map 存入 `{3: 0, 2: 1}`。 |
72 | | - 3. `i=2, v=4`:`complement=2`。Map 存在鍵 `2`,其索引為 `1`。匹配成功。 |
73 | | - 4. 返回 `[1, 2]`。 |
74 | | -- **狀態轉換**:Map 會動態紀錄掃描過的數字,將原本需要二次掃描的查找降為 $O(1)$。 |
| 77 | +- **Dry Run**:`nums = [3, 2, 4], target = 6` |
| 78 | + 1. `i = 0, v = 3`:`complement = 3`。Map 為空。存入 `{3: 0}`。 |
| 79 | + 2. `i = 1, v = 2`:`complement = 4`。Map 只有 `{3: 0}`。存入 `{3: 0, 2: 1}`。 |
| 80 | + 3. `i = 2, v = 4`:`complement = 2`。Map 存在 `2`,索引為 `1`。 |
| 81 | + 4. 返回 `[1, 2]`。正確。 |
| 82 | +- **關鍵狀態變遷**: |
| 83 | +```mermaid |
| 84 | +stateDiagram-v2 |
| 85 | + [*] --> Start |
| 86 | + Start --> i0: i=0, v=3, Map={} |
| 87 | + i0 --> i1: complement=3 not in Map, Map={3:0} |
| 88 | + i1 --> i2: i=1, v=2, complement=4 not in Map, Map={3:0, 2:1} |
| 89 | + i2 --> Found: i=2, v=4, complement=2 found in Map! |
| 90 | + Found --> [*] |
| 91 | +``` |
75 | 92 |
|
76 | 93 | ### E – Evaluate(總結與評估) |
77 | | -- **時間複雜度**:$O(n)$。我們只需遍歷陣列一次,且每次 Map 的存取與查找均為 $O(1)$。 |
78 | | -- **空間複雜度**:$O(n)$。最壞情況下需要將 $n$ 個元素存入 Map。 |
79 | | -- **權衡**:使用空間換取時間。這是處理「尋找特定配對」問題最經典的優化方式。 |
| 94 | +- **時間複雜度**:$O(n)$。我們只需對陣列進行一次線性掃描,Map 的插入與查找均為 $O(1)$。 |
| 95 | +- **空間複雜度**:$O(n)$。最壞情況下需要存儲 $n$ 個元素到 Map 中。 |
| 96 | +- **權衡**:我們使用了額外的空間(Hash Map)來換取時間複雜度的大幅提升(從 $O(n^2)$ 優化到 $O(n)$)。 |
80 | 97 |
|
81 | 98 | --- |
82 | 99 |
|
83 | 100 | ## Output 2: 面試官口語回答腳本(精簡可直接說) |
84 | 101 |
|
85 | 102 | ### 1️⃣ 開場:題目理解 |
86 | | -這題是要在陣列中找到兩個數字,讓它們加起來等於目標值 `target`,並回傳這兩個數字的索引。題目保證一定有一個解,且每個數字不能重複使用。 |
| 103 | +這是一道尋找配對的經典問題。題目要求我們在陣列中找到兩個數,其和等於目標值。核心限制是每個數字只能用一次,且保證一定有一個解。 |
87 | 104 |
|
88 | 105 | ### 2️⃣ 解法選擇說明 |
89 | | -我選擇使用 **Hash Map** 來解這題,因為它能將尋找「互補數字」的時間從 $O(n)$ 降到 $O(1)$。雖然暴力解用雙層迴圈也能做,但 $O(n^2)$ 的效率在數據量大時會太慢。 |
| 106 | +我選擇使用 **Hash Map** 來解這題,而不是暴力解。因為暴力解的雙層迴圈會導致 $O(n^2)$ 的時間複雜度,而透過 Hash Map,我們可以將搜尋「補數」的時間降到常數級別,整體效率提高到 $O(n)$。 |
90 | 107 |
|
91 | 108 | ### 3️⃣ 解題策略概覽 |
92 | | -我會遍歷一次陣列。對於每個數字,我先去計算「還差多少才到 target」,然後檢查這個「差額」是否已經存在 Hash Map 中。如果有,就代表找到了;如果沒有,我就把當前的數字和它的索引存進 Map,繼續往後看。 |
| 109 | +我會遍歷陣列一次。在遍歷到每個數字時,我會先去 Map 裡找看看「還差多少才到目標」。如果有找到,就代表這對數字齊了;如果沒找到,我就把當前的數字和索引記錄下來,讓後面的數字來匹配。 |
93 | 110 |
|
94 | 111 | ### 4️⃣ 寫程式時會補充的關鍵說明 |
95 | | -在實作時有兩個細節要注意: |
96 | | -1. 我們要「先檢查、再存入」,這樣可以保證我們不會找到自己,滿足題目「不能重複使用同一個元素」的要求。 |
97 | | -2. 在 Golang 中,使用 `if idx, ok := m[complement]; ok` 可以很優雅地同時完成查找和判斷是否存在。 |
| 112 | +在實作上,順序很重要:我們要「先檢查、後存入」。這樣可以自然地避免「同一個元素被使用兩次」的問題,因為 Map 裡存的永遠是「當前索引之前」的數字。另外,在 Go 語言中,我會利用 `ok` idiom 來檢查 Map 裡是否存在補數。 |
98 | 113 |
|
99 | 114 | ### 5️⃣ 快速 Dry Run 說明 |
100 | | -假設輸入 `[3, 2, 4]` 目標是 `6`。 |
101 | | -看到 `3` 時 Map 是空的,存入 `{3:0}`; |
102 | | -看到 `2` 時去找 `4`,沒找到,存入 `{2:1}`; |
103 | | -最後看到 `4` 時去找 `2`,在 Map 裡找到了索引 `1`,所以直接回傳 `[1, 2]`。 |
| 115 | +以 `target = 6`, 陣列 `[3, 2, 4]` 為例。 |
| 116 | +遇到 `3` 的時候,補數是 `3`,Map 還是空的,存入 `{3: 0}`; |
| 117 | +遇到 `2` 的時候,補數是 `4`,沒找到,存入 `{2: 1}`; |
| 118 | +最後看到 `4` 的時候,補數是 `2`,這時在 Map 裡找到了索引 `1`,於是成功回傳 `[1, 2]`。 |
104 | 119 |
|
105 | 120 | ### 6️⃣ 收尾總結 |
106 | | -這個演算法的**時間複雜度是 $O(n)$**,**空間複雜度也是 $O(n)$**。這是在這類尋找配對問題中最優的時間效率。 |
107 | | - |
108 | | ---- |
| 121 | +這個解法的**時間複雜度是 $O(n)$**,**空間複雜度也是 $O(n)$**。這是在這類配對搜尋問題中最優的時間效率。 |
0 commit comments