新闻详情

新闻详情

首页 / 资讯中心 / 详情

两数之和全解析:从暴力解法到哈希表最优解

发布时间:2026/9/28 6:27:16来源:尧图网络
两数之和全解析:从暴力解法到哈希表最优解
“两数之和”是力扣第1题也是无数人刷题之旅的第一站。我敢说十个刷过力扣的人里至少八个第一次提交这道题时写的是两层 for 循环然后盯着 O(n²) 的复杂度和超时提示陷入沉思。这道题表面上是“从数组里找两个数让它们的和等于目标值”但你真正把它吃透之后哈希表查值、空间换时间、双指针扫描、输入边界处理这一整套刷题基本功基本就都带出来了。这篇笔记我会从题目解读、三种解法对比、关键细节拆解、踩坑实录四个角度完整过一遍。不管你是刚准备刷题的新手还是已经刷了百八十题想回来查漏补缺的老手这篇文章都值得看完——尤其最后那部分关于重复元素和返回顺序的细节很多人刷了好几遍都没意识到。1. 题目到底在考什么先别急着写代码1.1 题干信息与隐藏条件先看题干。题目要求很简单给定一个整数数组nums和一个整数目标值target在数组中找出和为目标值的那两个整数并返回它们的数组下标。但题干里有几条“隐藏条件”很容易被忽略却直接影响你写代码的方式。第一条“每种输入只会对应一个答案”。意思是只要找到一组符合条件的数就能直接返回不需要继续往后找。很多人刷题时会下意识地收集所有答案结果写了多余代码。知道“只有一组解”这个前提你的代码就可以在找到答案后立刻终止。第二条“数组中同一个元素在答案里不能重复出现”。这个限制特别容易被新手忽略尤其在数组里有重复数字的时候。比如nums [3, 3]、target 6正确答案是[0, 1]而不是[0, 0]。后者的意思是把同一个下标的元素用了两次这不符合题意。第三条数组本身不保证有序。这一点在暴力解里无所谓但如果你想用双指针解法也就是后面提到的两数之和 II 的思路就得先排序。可排序会改变元素原本的下标所以原题这种“返回下标”的版本双指针并不能直接套用。这也是为什么很多人在 LeetCode 讨论区看到双指针解法后感到困惑——因为那双指针解法是针对“返回数值”的变体或者在排序后额外记录原下标的版本。为什么要抠这些隐藏条件因为实际面试时面试官不会完全照搬原题。他会改条件比如“如果没有解怎么办”“如果要求返回所有不重复的组合呢”“如果数组里有重复数字呢”。你只有在刷题时就对这些条件足够敏感才能在被追问时从容应对。1.2 为什么第一版总是两层循环几乎所有刷过这道题的人第一版代码都是暴力枚举外层循环取第一个数内层循环取第二个数两数相加等于target就返回下标。代码大概长这样def two_sum_brute(nums, target): n len(nums) for i in range(n): for j in range(i 1, n): if nums[i] nums[j] target: return [i, j] return []为什么大家都从暴力解开始因为它最简单、最不容易出错。在你对题目都还没完全理解的时候直接用最笨的办法先跑通是成本最低的验证方式。我个人的习惯是算法题先写暴力解确认题意理解正确再考虑优化。这看起来像是多此一举但实际上能帮你规避大量因题意理解偏差导致的返工。暴力解的问题是时间复杂度太高。外层循环 n 次内层循环平均 n/2 次总操作次数大约是 n²/2。LeetCode 的测试数据中n经常到 10⁴ 甚至 10⁵ 量级10⁵ 的平方是 10¹⁰ 次操作。Python 一秒钟能执行的简单操作大约是 10⁷ 到 10⁸ 次所以暴力解在数据量大的时候必然超时。这也是这道题存在的意义它逼着你引入一种新数据结构——哈希表来把时间复杂度从 O(n²) 降到 O(n)。2. 三种主流解法与完整实现2.1 暴力枚举最简单的 AC 方案虽然是暴力但代码里也有细节要注意。内层循环的起点必须是i 1而不是 0。很多新手在内层循环里写for j in range(n)这样会导致两个问题一是i和j相等时会比较同一个元素比如nums [3, 3]时会把nums[0] nums[0]判定为有效结果这违反了“同一个元素不能重复使用”的约束。二是做了大量重复比较nums[0]和nums[1]会比较nums[1]和nums[0]又会比较一次白白浪费计算。正确的内层循环起点是i 1这样每对组合只会被检查一次。暴力解在什么情况下是合理的选择答案是n很小的时候。如果面试官说数组长度最多只有几十那你直接暴力解完全没问题甚至更不容易出错。这也是一个重要的刷题经验不要盲目追求最优解要根据数据规模选择最合适的方案。上来就写哈希表当然没问题但如果数据量小暴力解写起来更快、调试更容易。2.2 两遍哈希表先建表再查找暴力解的时间瓶颈在于对于每个nums[i]你都要遍历剩下的所有元素来寻找target - nums[i]。这个“查找”操作如果能把 O(n) 降到 O(1)整体复杂度就是 O(n)。哈希表Python 里的dict就是干这个用的。我们可以先把数组里所有元素的值作为 key、下标作为 value 存进哈希表然后再次遍历数组对每个nums[i]直接查表看target - nums[i]是否存在。def two_sum_hashmap_two_pass(nums, target): hashmap {} for i, num in enumerate(nums): hashmap[num] i for i, num in enumerate(nums): complement target - num if complement in hashmap and hashmap[complement] ! i: return [i, hashmap[complement]] return []这段代码里有一行很关键hashmap[complement] ! i。为什么需要这个判断因为存在一种特殊场景nums[i]恰好等于target / 2。举个例子nums [3, 3]、target 6第一遍建表时hashmap[3]先存下标 0然后被覆盖成下标 1。第二遍遍历到i 0时complement 3在哈希表中存在但hashmap[3]的值是 1不是 0所以hashmap[complement] ! i成立正确返回[0, 1]。但如果输入是nums [3]、target 6第二遍遍历i 0时complement 3在哈希表中存在但hashmap[3] i说明找到的是同一个元素不能使用。如果没有! i这个判断代码就会错误地返回[0, 0]。两遍哈希表的时间复杂度是 O(n)空间复杂度是 O(n)。它是暴力解到一遍哈希表之间的“过渡版本”理解它能帮你更清楚地看到“为什么”一遍哈希表可行。2.3 一遍哈希表边遍历边查最优解两遍哈希表需要先完整建表再遍历查找。但实际上你完全可以在一次遍历中同时完成“查找”和“建表”两件事。核心思路遍历数组时对每个nums[i]先检查target - nums[i]是否在哈希表中。如果在直接返回如果不在就把nums[i]和它的下标存入哈希表继续遍历。def two_sum(nums, target): hashmap {} for i, num in enumerate(nums): complement target - num if complement in hashmap: return [hashmap[complement], i] hashmap[num] i return []同样用nums [3, 3]、target 6推演一遍i 0num 3complement 3哈希表为空查不到。把hashmap[3] 0存入。i 1num 3complement 3在哈希表中查到hashmap[3] 0返回[0, 1]。注意这里不需要额外的hashmap[complement] ! i判断。因为我们在把num存入哈希表之前就完成了查询所以查到的一定是之前遍历过的元素不可能是当前元素。这就是“先查后存”的顺序优势。我再给一个 JavaScript 版本方便用 JS 刷题的朋友直接参考function twoSum(nums, target) { const map new Map(); for (let i 0; i nums.length; i) { const complement target - nums[i]; if (map.has(complement)) { return [map.get(complement), i]; } map.set(nums[i], i); } return []; }三种解法对比如下解法时间复杂度空间复杂度适用场景暴力枚举O(n²)O(1)数据量小或确认题意两遍哈希表O(n)O(n)理解哈希表建表过程一遍哈希表O(n)O(n)最推荐代码简洁且高效3. 关键细节与“为什么”拆解3.1 哈希表查找为什么是 O(1)很多初学者只知道“用哈希表快”但说不清快在哪里。这里用一个类比你就能彻底理解。想象有一家酒店前台有一本“房客登记簿”。如果你要找一个叫“张三”的人住在哪个房间你可以选择挨个敲门问这就是线性查找O(n)。你也可以去前台查登记簿按“张三”这个名字直接找到他的房间号这就是哈希表查找平均 O(1)。哈希表内部做的事情其实是把 key比如数字 3通过一个哈希函数计算出一个数字再把这个数字映射到内部数组的某个位置。这个计算和定位过程不依赖数据总量所以耗时基本恒定。Python 的dict底层就是哈希表。key 先计算哈希值再定位到槽位如果发生哈希冲突Python 采用开放寻址法处理。Java 的HashMap则是链地址法加红黑树。这些底层细节在刷题阶段不用深究但你只需要记住平均情况下哈希表的插入和查找都是 O(1)这足以让两数之和这道题的复杂度从 O(n²) 降到 O(n)。这里顺便提醒一句哈希表的最坏情况是 O(n)因为如果所有 key 的哈希值都一样冲突会非常严重。但 Python 对整数、字符串这些内置类型的哈希实现质量很高刷题场景不用担心这个问题。3.2 为什么“先查后存”能解决重复元素问题这是两数之和里最容易讲明白、也最容易踩坑的一个点。如果你把“先查后存”的顺序反过来先存当前元素再查会怎样还是用nums [3, 3]、target 6i 0先存hashmap[3] 0再查complement 3此时查到hashmap[3] 0返回[0, 0]。这个结果是错误的因为下标 0 这个元素被用了两次。正确的流程是“先查后存”当前元素还没进哈希表时它只能“看见”已经遍历过的历史元素。这样即使数组里有重复元素你匹配到的也一定是两个不同下标的元素天然满足“同一个元素不能重复出现”的约束。3.3 返回顺序与下标规则这道题的输出要求是返回两个下标顺序无关紧要。return [hashmap[complement], i]和return [i, hashmap[complement]]都能 AC。但我建议统一写成“先返回查到的历史下标再返回当前下标”也就是[hashmap[complement], i]。这样你一眼就能看出前一个是之前存的后一个是当前遍历到的逻辑清晰不容易搞混。还有一个容易被忽略的点有些变种题要求“返回两个数的值”而不是下标。这时你的哈希表依然可以存下标但返回时要仔细看题目要求。刷题最忌讳的就是把做题模板背熟了结果改一个输出格式就懵了。4. 常见问题与排查技巧实录4.1 数组里有重复数字时哈希表索引被覆盖了怎么办这是问得最多的问题之一。场景nums [2, 2, 7, 11, 15]、target 9。两遍哈希表第一遍建表时hashmap[2]先被写成 0又被写成 1最终保留了下标 1。第二遍遍历到i 0时complement 7在哈希表中查到下标 2返回[0, 2]正确。看起来覆盖并没有造成问题因为题目保证了“只有一组有效答案”。但你要知道覆盖行为本身会丢失信息。如果题目改成“返回所有满足条件的组合”且数组里有多个相同的数你就不能用简单的hashmap[num] i覆盖写入了。这种情况下要么用hashmap[num] [i1, i2, ...]记录同一个值的多个下标要么换排序双指针思路去重。4.2 找不到答案时程序返回什么原题保证一定有解但你写代码时依然建议在循环结束后加一行默认返回值return []这样做的意义在于一是防止函数在没有返回值时隐式返回None二是在面试中被追问“如果没有解呢”时你已经提前做好了处理。面试官看到你的代码第一反应可能不是算法本身而是边界处理是否完善。4.3 尝试用测试用例跑一遍刷题笔记最大的价值除了代码就是测试用例。我每次写这道题都会拿这几组数据喂进去普通情况nums [2, 7, 11, 15]、target 9两个相同元素nums [3, 3]、target 6包含负数nums [-1, -2, -3, -4]、target -5包含 0nums [0, 1, 4, 0]、target 0找不到答案仅限扩展nums [1, 2, 3]、target 7每换一种写法就把这些用例跑一遍。尤其是“两个相同元素”和“包含负数”最容易暴露“先存后查”和“返回自己”的问题。4.4 一段可复用的刷题模板如果你在本地练习可以准备一个带测试的模板文件把测试用例一起写进去from typing import List def two_sum(nums: List[int], target: int) - List[int]: hashmap {} for i, num in enumerate(nums): complement target - num if complement in hashmap: return [hashmap[complement], i] hashmap[num] i return [] if __name__ __main__: tests [ ([2, 7, 11, 15], 9, [0, 1]), ([3, 3], 6, [0, 1]), ([-1, -2, -3, -4], -5, [0, 2]), ([0, 1, 4, 0], 0, [0, 3]), ] for nums, target, expected in tests: result two_sum(nums, target) print(nums, target, result, PASS if result expected else FAIL)这里用了typing.List做类型标注好处是代码可读性更高你在本地 IDE 里写的时候还能自动补全。刷题阶段把模板准备好能省下不少时间。5. 举一反三从两数之和到整个数组题体系5.1 变体一两数之和 II有序数组用双指针如果你遇到的是“升序排列的数组”比如 LeetCode 的 167 题那么可以用双指针把空间复杂度降到 O(1)。思路left指向数组开头right指向数组末尾。计算nums[left] nums[right]如果和小于target说明需要更大的数left右移如果和大于target说明需要更小的数right左移相等则返回。def two_sum_sorted(nums, target): left, right 0, len(nums) - 1 while left right: current_sum nums[left] nums[right] if current_sum target: return [left 1, right 1] elif current_sum target: left 1 else: right - 1 return []注意这里返回的下标是从 1 开始计数的因为题目可能这样要求。双指针为什么快因为数组有序指针移动时天然避开了大量无效组合时间复杂度 O(n)空间 O(1)。这里有一个很多人都会问的问题排序之后双指针适用于原题“两数之和”吗答案是如果题目要求返回的是原数组的下标那么不能直接排——排序会打乱下标。你需要在排序前记录每个元素的下标或者直接用哈希表方案。这也是为什么原题主流的解是一遍哈希表而不是双指针。5.2 变体二三数之和、四数之和三数之和LeetCode 15 题的核心套路是先排序然后固定一个数剩下的问题就是一个“有序数组的两数之和”用双指针解决。伪代码思路如下排序数组。外层指针i从 0 遍历到n - 3。如果nums[i]和上一个数相同跳过去重。内层用双指针left i 1、right n - 1找target -nums[i]假设三数之和为 0。找到一组后同时移动左右指针并跳过重复值。你会发现三数之和的核心依然是对“两数之和”思想的应用。这也是为什么把两数之和吃透如此重要——它是后续一系列数组问题的地基。四数之和LeetCode 18 题也类似无非是再套一层循环固定两个数剩下两个数用双指针。套路一样只是去重逻辑要更细心。5.3 面试官追问如果数据量很大怎么办这是一个很经典的开放性问题。两数之和的标准解法需要 O(n) 的额外空间但如果数组大到内存放不下比如数据在磁盘上哈希表方案就不适用了。你可以这样回答先分析瓶颈——内存无法容纳整个数组和哈希表此时需要分治思路把数据分块读入在每一块内部用哈希表或暴力解处理跨块的部分则需要外部排序后归并查找。或者如果你对数据分布有了解可以用近似索引结构进一步优化。这道追问的意图不是让你设计一套完整系统而是考察你是否清楚“哈希表用空间换时间”的代价。你只要说出“空间复杂度 O(n) 在大数据量下是瓶颈需要用分治或外部存储方案”这个方向就已经算合格了。6. 刷这道题最容易踩的 3 个坑实操心得6.1 坑一把当前元素先存进去再查导致匹配到自己这个坑我前面反复强调过。代码表现为hashmap[num] i if target - num in hashmap: return [i, hashmap[target - num]]当num * 2 target时这个写法会返回[i, i]。哪怕不返回[i, i]也可能因为覆盖了历史下标而漏掉正确答案。解决方法只有一个记住“先查后存”的顺序。如果你实在记不住就用两遍哈希表建表和查找分开再加! i判断也能保证正确。6.2 坑二只考虑正数忘了数组里有负数很多新手在本地测试时都用全正数用例导致代码在负数场景下暴露 bug。哈希表方案通常不受负数影响但暴力解和基于“排序 双指针”的写法如果没考虑负数逻辑可能会出错。比如nums [-1, -2, -3, -4]、target -5正确答案是[0, 2]。暴力解里依然是nums[i] nums[j] target逐对判断没区别但如果你自己优化成了“固定一个数在剩余部分用二分查找找 target - nums[i]”二分查找的前提是有序你就要确保剩余部分有序。数组无序时这些额外优化都要重新审视。我的建议是本地测试用例一定要包含负数、0、重复元素这三种情况。跑通它们你的代码在边界处理上基本就稳了。6.3 坑三暴力解内层循环从 0 开始做无效比对暴力解的经典错误是for i in range(n): for j in range(n): if i ! j and nums[i] nums[j] target: return [i, j]虽然加上了i ! j判断但这样会重复比较大量组合数据量稍大就容易超时。正确做法是内层从i 1开始一次比较只覆盖不同的组合。实际上我在刷这道题时有一个习惯先写暴力解跑通样例然后立刻改成一遍哈希表最后再对比两个版本的复杂度。这不是在做无用功而是刻意练习“从 O(n²) 到 O(n)”的优化思路。面试时如果时间紧张你甚至可以直接写一遍哈希表但如果面试官让你讲讲优化过程你能从暴力解推导出哈希表方案他会很满意。这个进阶过程比单纯背答案有价值得多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Jev模型接入Vercel AI Gateway:简历筛选自动化实践 2026/9/28 7:19:39

Jev模型接入Vercel AI Gateway:简历筛选自动化实践

最近 Jev 模型的热度一下子上来了,GitHub 和技术群里到处能看到有人问“Jev 密钥怎么申请”“Jev 能不能在 Codex 里用”。我倒是没赶潮流去聊大模型本身,而是直接把 Jev 接进了我的简历筛选小工具里,通过 Vercel AI Gateway 做统一的模型入口…

阅读更多 →
Vue 3组件通信与动态表单实战:四大姿势与高频坑解析 2026/9/28 7:19:39

Vue 3组件通信与动态表单实战:四大姿势与高频坑解析

学 Vue 3 的兄弟,很多人会在第四章卡壳。前面几章你把模板语法、响应式基础都过了一遍,感觉什么都懂了,可一进入组件通信、computed、动态表单这些场景,马上就开始混。第四章在整个 Vue 3 学习路径里就是一道分水岭——它不再教你…

阅读更多 →
SurveyKing 开源问卷系统源码拆解:Java 后端与二次开发实战 2026/9/28 7:19:39

SurveyKing 开源问卷系统源码拆解:Java 后端与二次开发实战

简介:这是一套基于Java开发的开源问卷系统SurveyKing完整源码,面向需要搭建问卷平台的后端开发者、全栈工程师及技术团队,可用于市场调研、教育反馈、企业内部信息收集等场景,帮助读者快速获得一套可二次开发、可私有化部署的问卷…

阅读更多 →
用Dify从零搭建AI复盘助手hindsight:完整实操指南 2026/9/28 7:19:39

用Dify从零搭建AI复盘助手hindsight:完整实操指南

1. 项目概述:hindsight 到底在解决什么问题hindsight 这个词直译过来是「事后」,引申一下就是「事后洞察」,说白了就是常说的"事后诸葛"。最近我注意到hindsight这个关键词的热度明显在涨,把它和Dify放在一起搜的人尤其…

阅读更多 →
易拉罐底部缺陷检测实战:VOC转YOLO与YOLOv8训练全流程 2026/9/28 7:19:39

易拉罐底部缺陷检测实战:VOC转YOLO与YOLOv8训练全流程

简介:面向工业质检、目标检测入门及毕设场景的易拉罐底部缺陷检测数据集,包含1122张标注图片与3308个真实标注框,覆盖FB、can、hole、scratch、stamped共5个类别,可用于训练缺陷分类与定位模型。数据同时提供Pascal VOC和YOLO两种…

阅读更多 →
S7-200 SMART双PLC以太网TCP通信实战:从配置到避坑全记录 2026/9/28 7:19:32

S7-200 SMART双PLC以太网TCP通信实战:从配置到避坑全记录

前阵子做一个产线改造,现场分成两个控制柜,各装了一台S7-200 SMART。甲方要求两台柜子必须联动:A柜的出料完成信号要送到B柜,B柜的故障和急停状态要实时回传到A柜,产量数据两边还要互相抄读。第一反应是拉硬线&#xf…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉