一、题目描述

原题

给定一个整数数组 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
题目 –> 思路 –> 解法 –> 复杂度 –> 记忆
声明:本站(华域联盟www.cnhackhy.com)所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。