前言「两数之和」要求在一个整数数组里找出两个数使它们的和等于目标值并返回这两个数的下标。它是哈希表最经典的应用场景也是很多人接触「用空间换时间」的第一个例子。题目本身不难但要把边界写对、把重复元素处理对、把返回值约定说清楚仍然有不少细节。最常见的误解是「先建表再查找」。如果先把整个数组塞进哈希表再遍历查找补数那么当两个下标指向同一个元素时例如目标值是某个元素的两倍会错误地把同一个位置用两次。正确做法是「边查边建」对当前元素先问表里有没有它的补数没有再把当前元素放进去。这样保证查到的补数一定来自更早的位置两个下标天然不同。第二个误解是认为哈希表解法的复杂度是严格 O(n)。准确说法是平均 O(n)最坏情况取决于哈希函数与冲突处理理论上可以退化。本文会说明这一点并给出暴力法、哈希法、排序双指针法三种实现及各自的适用条件。一、问题定义与返回值约定输入是一个整数序列nums和一个目标值target要求返回一对下标(i, j)满足i ! j且nums[i] nums[j] target。题目通常保证有且仅有一组答案但工程代码不应依赖这个假设需要定义「无解」时返回什么。返回类型的选择会影响接口的清晰度。如果确定有解可以返回std::pairstd::size_t, std::size_t如果可能无解用std::optionalstd::pair...更直接如果可能有多组解返回std::vectorstd::vectorstd::size_t。用「返回空 vector」表示无解是一种常见约定但它与「答案就是空」无法区分严格来说不如std::optional明确。下标类型建议用std::size_t而不是int。std::vector的size()返回std::size_t用int接收会引入有符号与无符号的混用在超大数组上还可能溢出。代价是打印时要记得用%zu或先转换。二、暴力法与它的价值暴力法枚举所有i j检查和是否等于目标。时间复杂度 O(n²)空间 O(1)。它在面试里通常不是最终答案但有两个价值一是作为对照实现用来验证优化版本的结果二是当 n 很小比如小于 32时它的常数因子低实际可能比哈希表更快。// 适用于 C17#include cstddef#include utility#include vector#include optionalstd::optionalstd::pairstd::size_t, std::size_ttwo_sum_brute(const std::vectorint nums, int target) {for (std::size_t i 0; i nums.size(); i) {for (std::size_t j i 1; j nums.size(); j) {// 用 long long 计算和避免 int 相加溢出if (static_castlong long(nums[i]) nums[j] target) {return std::make_pair(i, j);}}}return std::nullopt;}这里把加法提升到long long不是为了性能而是为了正确性两个int相加可能溢出而有符号整数溢出是未定义行为undefined behavior, UB不能写成「溢出后回绕成负数再比较」。在int为 32 位、target也接近边界时这个细节会真的影响结果。三、哈希表边查边建哈希法的核心是把「找补数」从线性扫描变成常数时间查询。遍历数组对每个元素x计算need target - x如果表里已经有need说明之前某个位置与当前位置构成答案否则把x和它的下标存入表中。顺序很重要先查后插。如果先插入再查当前元素会查到自己当2 * x target时会返回同一个下标两次。// 适用于 C17#include cstddef#include optional#include unordered_map#include utility#include vectorstd::optionalstd::pairstd::size_t, std::size_ttwo_sum_hash(const std::vectorint nums, int target) {std::unordered_maplong long, std::size_t seen;seen.reserve(nums.size()); // 预留桶数减少 rehashfor (std::size_t i 0; i nums.size(); i) {const long long need static_castlong long(target) - nums[i];auto it seen.find(need);if (it ! seen.end()) {return std::make_pair(it-second, i);}seen.emplace(static_castlong long(nums[i]), i);}return std::nullopt;}键类型用long long而不是int是为了让need的计算不溢出。若用int作键target - nums[i]在极端值下会溢出UB而且溢出后的值可能恰好命中表里的某个键产生错误答案。用long long存键后nums[i]转成long long再参与减法结果一定在long long范围内。重复元素在这套逻辑下是安全的。以nums {3, 3}、target 6为例i 0 时need 3表为空插入3 → 0i 1 时need 3命中返回(0, 1)。两个下标不同答案正确。如果数组是{3, 1, 3}、target 6i 2 时命中 0返回(0, 2)同样正确。reserve(nums.size())不是必须的但它能减少插入过程中的重新哈希。unordered_map的桶数会随元素增加而增长每次增长都要重新分布所有元素提前预留可以把这个开销摊掉。代价是内存占用略高。至于reserve之后是否还会 rehash取决于实现的最大负载因子标准只规定平均情况下的复杂度不规定具体的桶数与增长策略。复杂度平均 O(n) 时间、O(n) 空间。最坏情况下如果所有键都落到同一个桶哈希函数被恶意构造或质量很差单次查找退化为 O(n)整体 O(n²)。这也是「哈希表严格 O(n)」说法不准确的原因。实际使用中标准库的哈希函数对整数通常是恒等映射再混合配合质数桶数退化很少发生但不能当成数学保证。四、排序加双指针另一种思路如果数组已经有序可以用双指针左指针指向最小元素右指针指向最大元素计算两数之和和小于目标就左指针右移和大于目标就右指针左移。时间 O(n)空间 O(1)。但题目要求返回原始下标排序会打乱下标所以必须把「值 原下标」一起排序或者先复制一份带下标的数组。// 适用于 C17#include algorithm#include cstddef#include optional#include utility#include vectorstd::optionalstd::pairstd::size_t, std::size_ttwo_sum_sorted(std::vectorint nums, int target) {std::vectorstd::pairint, std::size_t items;items.reserve(nums.size());for (std::size_t i 0; i nums.size(); i) {items.emplace_back(nums[i], i);}std::sort(items.begin(), items.end());std::size_t lo 0;std::size_t hi items.size();while (lo 1 hi) {const long long sum static_castlong long(items[lo].first) items[hi - 1].first;if (sum target) {return std::make_pair(items[lo].second, items[hi - 1].second);} else if (sum target) {lo;} else {--hi;}}return std::nullopt;}这种写法适合「数组已排序」或「需要多次查询同一数组」的场景排序一次 O(n log n)之后每次查询 O(n)。如果只查一次排序的 O(n log n) 反而比哈希的 O(n) 慢除非内存受限、不能用哈希表。注意hi初始化为items.size()而不是size() - 1循环条件是lo 1 hi这样hi - 1始终是有效下标空数组时循环一次也不进。这是半开区间写法在双指针上的应用。五、三种方法对比方法平均时间最坏时间额外空间是否保留原下标适用场景暴力枚举O(n²)O(n²)O(1)是小数组、对照验证哈希表O(n)O(n²)O(n)是通用单次查询排序双指针O(n log n)O(n log n)O(n)需要额外记录已排序或多次查询选型的判断顺序是先看数组是否已经有序有序就用双指针否则看内存是否宽裕宽裕就用哈希两者都不满足才退回暴力。不要为了「看起来高级」在小数组上强行引入哈希表构造与销毁哈希表的开销在 n 很小时不可忽略。常见坑点坑点一先插入再查找导致同一元素被用两次。❌seen[nums[i]] i;写在find之前nums {3}、target 6时返回(0, 0)。✅ 先find(need)未命中再插入当前元素。坑点二用int计算target - nums[i]。❌int need target - nums[i];极端值下溢出属于 UB。✅ 提升到long long计算键类型也用long long。坑点三用int表示下标。❌for (int i 0; i nums.size(); i)有符号与无符号比较产生警告。✅ 用std::size_t或 C20 起用std::ranges::ssize获取有符号长度。坑点四返回空 vector 表示无解却当成有解处理。❌ 调用方不检查result.empty()就直接取result[0]。✅ 用std::optional表达「可能无解」或调用前显式判断。坑点五以为哈希表最坏也是 O(n)。❌ 在复杂度分析里写「哈希法严格 O(n)」。✅ 写「平均 O(n)最坏 O(n²)取决于哈希质量与冲突」。坑点六排序双指针直接对原数组排序丢失原始下标。❌std::sort(nums.begin(), nums.end());之后返回排序后的位置。✅ 把值和原下标绑成 pair 一起排序返回second。坑点七双指针的右边界初始化错位。❌std::size_t hi nums.size() - 1;在空数组时下溢为SIZE_MAX。✅ 用半开区间hi nums.size()循环条件lo 1 hi。坑点八认为题目保证有解就不写无解分支。❌ 函数末尾没有return编译器报「控制流到达非 void 函数末尾」。✅ 显式返回std::nullopt或空容器。总结要点结论核心思路用哈希表把「找补数」降为常数时间关键顺序先查后插避免同元素复用溢出防护补数与和都用long long计算重复元素边查边建天然正确处理下标类型优先std::size_t无解表达std::optional优于空容器两数之和的代码只有十几行真正需要想清楚的是三件事查询与插入的先后、加法是否会溢出、无解时返回什么。把这三点定下来哈希版本就是正确且高效的剩下的排序双指针只是另一种权衡在数组本来就有序时才有优势。
企业数字化 ERP 产品动态
相关推荐
Field II超声仿真中数组索引错误的排查与解决 1. 问题现象与背景解析这个报错信息"超声PSFfield II:位置 1 处的索引无效。数组索引必须为正整数或逻辑值..."在医学图像处理和超声仿真领域相当典型。我处理过不下20例类似案例,主要出现在使用Field II超声仿真工具箱进行点扩散函数(PSF)建模… · 2026/11/5 2:19:49
关系数据库设计:关系模式与完整性约束详解 1. 关系数据库的核心基石:关系模式与完整性约束第一次接触数据库设计时,我曾在关系模式和完整性约束这两个概念上栽过跟头。记得当时为了赶项目进度,直接在MySQL里建表插数据,结果第二天就发现订单表里出现了不存在的客户ID&#… · 2026/11/5 2:19:49
STM32步进电机蓝牙串口定位与梯形加减速控制 简介:面向嵌入式初学者与STM32开发者的步进电机无线控制工程,围绕串口蓝牙通讯、定时器脉冲输出与电机驱动策略展开,可用于解决手机APP远程控制电机正反转、调速与定位的实际问题。压缩包共479个文件,约71.95MB,以80个… · 2026/11/5 2:19:49
Docker Compose 下载与更新全攻略:在线镜像加速与离线安装指南 Docker Compose 这个工具,但凡你碰过容器化部署,基本绕不开它。可真正让人卡住的往往不是写 YAML,而是第一步——把它弄到机器上,还得是对应的版本。尤其在国内网络环境下,直接从官方源拉取二进制文件,十次… · 2026/11/5 3:01:06
幽默化设计:让家务变娱乐的5大策略 1. 项目背景与核心创意解析去年夏天的一次家庭聚会上,我意外发现一个有趣现象:当我把洗碗过程配上夸张的肢体动作和即兴喜剧台词时,原本抱怨洗碗的亲友们竟然主动加入了这个"表演"。这个偶然发现让我意识到,日常家务完全… · 2026/11/5 3:01:06
Windows Install Clean Up:MSI缓存清理与安装残留处理指南 1. 这个工具到底在清理什么:从MSI缓存机制说起很多人第一次听说 Windows Install Clean Up,是在某台老机器磁盘告急、翻遍“添加或删除程序”也找不到那几个占空间大户的时候。这个工具的名字直译过来就是“Windows 安装清理”,它针对的不是普… · 2026/11/5 3:01:06
Circuitscape安装避坑指南:Julia+Python+GDAL版本精准匹配 简介:本资源为生态连通性分析领域核心工具——Circuitscape软件包集,面向生态学研究者、景观规划师及GIS空间分析初学者,解决生物迁移路径识别、生态廊道优化与障碍物量化评估等关键问题。压缩包共264个文件,95.49MB,涵… · 2026/11/5 3:01:06
OpenClaw:AI驱动的自动化工作流提升40%效率 1. 项目概述:当AI工具遇上生产力革命最近在技术社区里OpenClaw这个工具突然火了起来,不少同行都在讨论如何用它来提升工作效率。作为一个长期关注生产力工具的技术从业者,我花了三周时间深度测试了这款工具,发现它确实能带来显著的… · 2026/11/5 3:01:06
WaferMap格式转换工具:免费解决晶圆图数据格式混乱 芯片良率分析这个圈子里,WaferMap 数据的格式混乱是个老生常谈的痛点。不同测试机台、不同封装厂、不同 EDA 工具导出的晶圆图文件,格式五花八门,有的用纯文本坐标列表,有的用带表头的 CSV,有的干脆是二进制加自定义头… · 2026/11/5 3:00:06
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
多智能体协作系统架构设计与实践优化 1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39
大模型系统性入门实战指南:从原理到部署微调的全链路路线 提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39
鸿蒙文件系统沙箱机制解析与开发实践 1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38