博客 / 詳情

返回

LeetCode 1:兩數之和(Two Sum)

一、題目描述

原題

給定一個整數數組 nums 和一個整數目標值 target,請你在該數組中找出和為目標值 target 的那兩個整數,並返回它們的數組下標。

你可以假設每種輸入只會對應一個答案,並且你不能使用兩次相同的元素。

你可以按任意順序返回答案。

示例

示例 1:
輸入:nums = [2,7,11,15], target = 9
輸出:[0,1]
解釋:因為 nums[0] + nums[1] == 9 ,返回 [0, 1]

示例 2:
輸入:nums = [3,2,4], target = 6
輸出:[1,2]

示例 3:
輸入:nums = [3,3], target = 6
輸出:[0,1]

約束條件

  • 2 <= nums.length <= 10⁴
  • -10⁹ <= nums[i] <= 10⁹
  • -10⁹ <= target <= 10⁹
  • 只會存在一個有效答案

二、題目解析

核心問題拆解

關鍵點 分析
輸入 整數數組 + 目標值
輸出 兩個數的下標(不是數值本身)
約束 同一元素不能使用兩次
保證 有且僅有一個解

思考方向

flowchart LR A[原問題] --> B["nums[i] + nums[j] = target"] B --> C[轉化思維] C --> D["對於每個 nums[i]<br/>尋找 target - nums[i]"] D --> E[如何快速查找?] E --> F["哈希表 O(1)"]

三、算法解答


算法一:暴力枚舉法

1. 算法原理描述

核心思想:窮舉所有可能的兩數組合,檢查它們的和是否等於目標值。

實現方式

  • 使用兩層循環
  • 外層循環固定第一個數 nums[i]
  • 內層循環遍歷其後的所有數 nums[j](j > i)
  • 檢查 nums[i] + nums[j] == target

2. 算法解答過程

nums = [2, 7, 11, 15], target = 9 為例:

外層 i 內層 j nums[i] nums[j] 是否等於 target
0 1 2 7 9 ✅ 找到!

返回 [0, 1]

3. 算法原理圖像解析

flowchart TD subgraph 數組["數組 nums, target = 9"] A0["[0]=2"] --- A1["[1]=7"] --- A2["[2]=11"] --- A3["[3]=15"] end subgraph 流程["暴力枚舉流程"] S[開始] --> L1["外層循環 i = 0"] L1 --> L2["內層循環 j = 1"] L2 --> C1{"nums[0] + nums[1]<br/>= 2 + 7 = 9<br/>== target?"} C1 -->|"✅ 是"| R["返回 [0, 1]"] C1 -->|"❌ 否"| L3["j++, 繼續內層"] L3 --> L4["若內層結束<br/>i++, 繼續外層"] end subgraph 複雜度["複雜度分析"] T["⏱ 時間: O(n²)"] SP["💾 空間: O(1)"] end

4. 算法代碼

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int n = nums.size();
        
        // 外層循環:固定第一個數
        for (int i = 0; i < n - 1; i++) {
            // 內層循環:遍歷第一個數之後的所有數
            for (int j = i + 1; j < n; j++) {
                // 檢查兩數之和是否等於目標值
                if (nums[i] + nums[j] == target) {
                    return {i, j};
                }
            }
        }
        
        // 題目保證有解,這裏不會執行到
        return {};
    }
};

5. 複雜度分析

複雜度類型 説明
時間複雜度 O(n²) 兩層嵌套循環,最壞情況遍歷 n(n-1)/2 次
空間複雜度 O(1) 只使用常數額外空間

6. 使用範圍

場景 適用性
數據規模小(n < 1000) ✅ 適用
數據規模大 ❌ 會超時
對空間要求極高 ✅ 適用
面試中作為初始解法 ✅ 適用,但需優化

算法二:哈希表法(一遍哈希)⭐ 最優解

1. 算法原理描述

核心思想:將「尋找兩數之和」轉化為「尋找差值」問題。

關鍵轉換

nums[i] + nums[j] = target
         ↓ 轉化
nums[j] = target - nums[i]

實現方式

  • 使用哈希表存儲已遍歷過的元素及其下標
  • 對於當前元素 nums[i],計算 complement = target - nums[i]
  • 在哈希表中查找 complement 是否存在
  • 若存在,説明找到答案;若不存在,將當前元素加入哈希表

優勢:哈希表的查找時間複雜度為 O(1),整體只需一次遍歷。

2. 算法解答過程

nums = [2, 7, 11, 15], target = 9 為例:

步驟 當前元素 complement 哈希表查找 操作 哈希表狀態
1 nums[0]=2 9-2=7 7 不存在 存入
2 nums[1]=7 9-7=2 2 存在!下標0 返回 [0,1] -

3. 算法原理圖像解析

flowchart TD subgraph 輸入["輸入: nums = [2,7,11,15], target = 9"] direction LR N0["2"] --> N1["7"] --> N2["11"] --> N3["15"] end subgraph Step1["步驟1: 處理 nums[0] = 2"] S1A["計算 complement = 9 - 2 = 7"] S1B{"哈希表中<br/>查找 7"} S1C["❌ 未找到"] S1D["存入哈希表<br/>{2: 0}"] S1A --> S1B --> S1C --> S1D end subgraph Step2["步驟2: 處理 nums[1] = 7"] S2A["計算 complement = 9 - 7 = 2"] S2B{"哈希表中<br/>查找 2"} S2C["✅ 找到! 下標=0"] S2D["返回 [0, 1]"] S2A --> S2B --> S2C --> S2D end 輸入 --> Step1 --> Step2 subgraph 要點["🔑 核心要點"] K1["邊遍歷邊建表"] K2["先查找再存入"] K3["O(1) 查找"] end

哈希表狀態變化:

flowchart LR subgraph T1["初始狀態"] E1["空 ∅"] end subgraph T2["處理2後"] H1["2 → 0"] end subgraph T3["處理7時"] H2["2 → 0"] F["查找2 ✅"] end T1 -->|"存入 2:0"| T2 -->|"查找2"| T3

4. 算法代碼

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        // 哈希表:key = 數值,value = 下標
        unordered_map<int, int> hashMap;
        
        for (int i = 0; i < nums.size(); i++) {
            // 計算當前元素的"配對值"
            int complement = target - nums[i];
            
            // 在哈希表中查找配對值
            if (hashMap.find(complement) != hashMap.end()) {
                // 找到了!返回兩個下標
                return {hashMap[complement], i};
            }
            
            // 未找到,將當前元素存入哈希表
            hashMap[nums[i]] = i;
        }
        
        // 題目保證有解,這裏不會執行到
        return {};
    }
};

5. 代碼詳解

// 關鍵點解析:

// 1. 為什麼用 unordered_map 而不是 map?
//    - unordered_map 基於哈希表,查找/插入 O(1)
//    - map 基於紅黑樹,查找/插入 O(log n)

// 2. 為什麼先查找再存入?
//    - 避免同一元素被使用兩次
//    - 例如:nums = [3, 3], target = 6
//    - 第一個3存入後,第二個3能找到第一個3

// 3. hashMap.find() vs hashMap.count()
//    - find() 返回迭代器,未找到返回 end()
//    - count() 返回 0 或 1
//    - 兩種寫法都可以,find() 更常用

6. 複雜度分析

複雜度類型 説明
時間複雜度 O(n) 只需遍歷數組一次,哈希表操作 O(1)
空間複雜度 O(n) 最壞情況需存儲 n-1 個元素

7. 使用範圍

場景 適用性
數據規模大 ✅ 最優解
需要快速查找 ✅ O(1) 查找
對空間敏感 ⚠️ 需要額外空間
需要返回下標 ✅ 適用
數組無序 ✅ 適用

算法三:排序 + 雙指針法(特殊場景)

⚠️ 注意:此方法會丟失原始下標,需要額外處理。僅適用於返回值而非下標的變體題目。

1. 算法原理描述

核心思想:利用有序數組的性質,使用雙指針從兩端向中間逼近。

實現方式

  1. 將數組排序
  2. 設置左指針 left = 0,右指針 right = n-1
  3. 計算 sum = nums[left] + nums[right]
    • sum == target:找到答案
    • sum < target:左指針右移(增大和)
    • sum > target:右指針左移(減小和)

2. 算法解答過程

nums = [2, 7, 11, 15], target = 9 為例(已排序):

步驟 left right nums[left] nums[right] sum 與target比較 操作
1 0 3 2 15 17 17 > 9 right--
2 0 2 2 11 13 13 > 9 right--
3 0 1 2 7 9 9 == 9 ✅ 找到

3. 算法原理圖像解析

flowchart TD subgraph 初始["排序後數組: [2, 7, 11, 15], target = 9"] direction LR A["2"] --- B["7"] --- C["11"] --- D["15"] end subgraph R1["第1輪"] R1L["L→2"] R1R["15←R"] R1C["2 + 15 = 17 > 9"] R1A["R左移 ←"] R1L --- R1R R1C --> R1A end subgraph R2["第2輪"] R2L["L→2"] R2R["11←R"] R2C["2 + 11 = 13 > 9"] R2A["R左移 ←"] R2L --- R2R R2C --> R2A end subgraph R3["第3輪 ✅"] R3L["L→2"] R3R["7←R"] R3C["2 + 7 = 9 == 9"] R3A["找到答案!"] R3L --- R3R R3C --> R3A end 初始 --> R1 --> R2 --> R3

雙指針移動規則:

flowchart TD S["計算 sum = nums[L] + nums[R]"] --> C{sum 與 target 比較} C -->|"sum == target"| F["✅ 找到答案"] C -->|"sum < target"| L["L++ 需要更大的數"] C -->|"sum > target"| R["R-- 需要更小的數"] L --> S R --> S

4. 算法代碼

// 方法:保留原始下標的雙指針解法
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        // 創建副本並排序,保留原始下標
        vector<pair<int, int>> numWithIndex;
        for (int i = 0; i < nums.size(); i++) {
            numWithIndex.push_back({nums[i], i});
        }
        sort(numWithIndex.begin(), numWithIndex.end());
        
        int left = 0, right = nums.size() - 1;
        
        while (left < right) {
            int sum = numWithIndex[left].first + numWithIndex[right].first;
            
            if (sum == target) {
                return {numWithIndex[left].second, numWithIndex[right].second};
            } else if (sum < target) {
                left++;
            } else {
                right--;
            }
        }
        
        return {};
    }
};

5. 複雜度分析

複雜度類型 説明
時間複雜度 O(n log n) 排序 O(n log n) + 雙指針 O(n)
空間複雜度 O(n) 需要存儲原始下標

6. 使用範圍

場景 適用性
數組已排序 ✅ 最佳選擇
返回值而非下標 ✅ 適用
需要返回下標 ⚠️ 需額外處理
Two Sum II(有序數組) ✅ 標準解法

四、三種算法對比

flowchart LR subgraph 暴力法["暴力枚舉"] B1["⏱ O n²"] B2["💾 O 1"] B3["⭐⭐"] end subgraph 哈希表["哈希表法 ✅推薦"] H1["⏱ O n"] H2["💾 O n"] H3["⭐⭐⭐⭐⭐"] end subgraph 雙指針["排序+雙指針"] D1["⏱ O n log n"] D2["💾 O n"] D3["⭐⭐⭐"] end 暴力法 -->|"優化查找"| 哈希表 暴力法 -->|"有序場景"| 雙指針
算法 時間複雜度 空間複雜度 適用場景 推薦指數
暴力枚舉 O(n²) O(1) 小數據、空間受限 ⭐⭐
哈希表法 O(n) O(n) 通用最優解 ⭐⭐⭐⭐⭐
排序+雙指針 O(n log n) O(n) 有序數組、返回值 ⭐⭐⭐

💡 面試建議

flowchart LR A["1️⃣ 先説暴力解法"] --> B["2️⃣ 分析瓶頸<br/>重複查找"] B --> C["3️⃣ 引出哈希表優化"] C --> D["4️⃣ 給出最優解"]

五、知識點總結

1. 涉及的數據結構

數據結構 用途 關鍵操作
數組 存儲原始數據 遍歷 O(n)
哈希表 快速查找 查找/插入 O(1)

2. 涉及的算法思想

mindmap root((兩數之和<br/>核心思想)) 暴力枚舉 窮舉所有可能 基礎解法 空間換時間 用額外空間 降低時間複雜度 問題轉化 找和轉找差 核心優化思路 雙指針 有序數組 兩端逼近

3. C++ 知識點

// 1. unordered_map 的使用
#include <unordered_map>
unordered_map<int, int> map;
map[key] = value;           // 插入
map.find(key);              // 查找,返回迭代器
map.count(key);             // 返回 0 或 1
map.end();                  // 結束迭代器

// 2. vector 的初始化返回
return {a, b};              // 直接返回初始化列表

// 3. pair 的使用
pair<int, int> p = {value, index};
p.first;                    // 第一個元素
p.second;                   // 第二個元素

六、做題模板

「兩數之和」類問題通用模板

/*
 * 兩數之和問題模板
 * 適用於:在數組中尋找滿足 nums[i] + nums[j] = target 的配對
 */
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        // Step 1: 創建哈希表
        // key: 數組元素值
        // value: 元素下標
        unordered_map<int, int> hashMap;
        
        // Step 2: 遍歷數組
        for (int i = 0; i < nums.size(); i++) {
            // Step 3: 計算配對值
            int complement = target - nums[i];
            
            // Step 4: 在哈希表中查找配對值
            if (hashMap.find(complement) != hashMap.end()) {
                // 找到配對,返回結果
                return {hashMap[complement], i};
            }
            
            // Step 5: 將當前元素加入哈希表
            hashMap[nums[i]] = i;
        }
        
        // Step 6: 未找到(題目保證有解時不會執行)
        return {};
    }
};

模板流程圖:

flowchart TD A[開始] --> B["創建空哈希表"] B --> C["遍歷數組 i = 0 to n-1"] C --> D["計算 complement = target - nums[i]"] D --> E{"complement<br/>在哈希表中?"} E -->|"是"| F["返回 hashMap[complement], i"] E -->|"否"| G["hashMap[nums[i]] = i"] G --> H{"i < n-1?"} H -->|"是"| C H -->|"否"| I["返回空 無解"]

模板變體

變體1:返回所有配對

vector<vector<int>> twoSumAll(vector<int>& nums, int target) {
    unordered_map<int, vector<int>> hashMap;  // 存儲所有下標
    vector<vector<int>> result;
    
    for (int i = 0; i < nums.size(); i++) {
        int complement = target - nums[i];
        if (hashMap.count(complement)) {
            for (int j : hashMap[complement]) {
                result.push_back({j, i});
            }
        }
        hashMap[nums[i]].push_back(i);
    }
    return result;
}

變體2:有序數組(雙指針)

vector<int> twoSumSorted(vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1;
    
    while (left < right) {
        int sum = nums[left] + nums[right];
        if (sum == target) {
            return {left, right};  // 注意:這裏返回的是下標
        } else if (sum < target) {
            left++;
        } else {
            right--;
        }
    }
    return {};
}

七、相關題目推薦

flowchart TD A["1. 兩數之和"] --> B["167. 兩數之和 II<br/>有序數組"] A --> C["15. 三數之和"] C --> D["18. 四數之和"] A --> E["454. 四數相加 II"]
題號 題目 難度 關聯知識點
167 兩數之和 II - 輸入有序數組 中等 雙指針
15 三數之和 中等 雙指針 + 去重
18 四數之和 中等 遞歸 + 雙指針
454 四數相加 II 中等 哈希表分組

八、面試高頻問答

Q1: 為什麼哈希表法要先查找再存入?

:為了避免同一元素被使用兩次。例如 nums = [3], target = 6,如果先存入再查找,會錯誤地返回 [0, 0]

Q2: 如果有多個答案怎麼辦?

:題目保證只有一個答案。若需返回所有答案,需要使用 unordered_map<int, vector<int>> 存儲所有下標。

Q3: 哈希表 vs 紅黑樹(map vs unordered_map)?

  • unordered_map:哈希表,查找 O(1),無序
  • map:紅黑樹,查找 O(log n),有序

本題不需要有序,選擇 unordered_map 更優。

Q4: 時間複雜度 O(n) 是最優嗎?

:是的。至少需要遍歷一次數組才能確定答案,O(n) 是下界。


九、一圖總結

flowchart TB subgraph 題目["📝 題目: 兩數之和"] Q["找兩個數 和為target<br/>返回下標"] end subgraph 思路["💡 核心思路"] T["找和轉找差<br/>nums[j] = target - nums[i]"] end subgraph 解法["🔧 最優解法"] S1["哈希表存儲已遍歷元素"] S2["O 1 查找配對值"] S3["邊遍歷邊建表"] end subgraph 複雜度["⚡ 複雜度"] C1["時間: O n"] C2["空間: O n"] end subgraph 記憶["📌 記憶口訣"] M["兩數之和哈希表<br/>邊查邊存是關鍵<br/>先找差值後存入<br/>一次遍歷搞定它"] end 題目 --> 思路 --> 解法 --> 複雜度 --> 記憶
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.