尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
从双指针到前缀和:力扣数组题高频模板与边界避坑指南
我见过不少人刚开始刷力扣时总喜欢挑那些听起来高级的题目来做结果被虐了两天又默默回到数组题补基础。数组在力扣里确实属于最入门的结构但“入门”和“简单”其实是两回事它坑起来一点不比树和图少只是坑的类型不一样。这篇我结合自己刷数组练习的经历聊聊力扣数组题到底在考什么、有哪些高频模板、怎么避开那些看着不起眼却能让你连续交好几次WA的边界条件以及一套可以直接抄作业的刷题节奏。如果你正准备系统刷算法或者刷了一段时间却总在数组题上卡壳这篇应该能给你一些参考。我会尽量少讲虚的多给我实际用过、验证过的思路和代码骨架。1. 别小看数组题它在力扣题库里的真实地位与考察套路1.1 数组几乎是一切算法的底层载体很多人有个错觉觉得数组题太基础刷它不如刷链表、二叉树、动态规划显得有水平。但事实是链表可以用数组模拟字符串可以转换成字符数组处理矩阵本身就是二维数组树的前中后序遍历虽然用的是递归但层序遍历经常要借助数组/队列来存节点。可以说数组是算法的“最小公倍数”几乎所有题目最后都要落到某种线性结构上。力扣的题库里数组题也是数量最多的类别之一从简单到困难都有覆盖。简单题考验你编码是否严谨中等题开始叠加双指针、滑动窗口、前缀和、哈希映射、二分查找这些常见算法困难题则把数组和动态规划、贪心、线段树这些内容揉在一起。所以我一直觉得刷数组题不是“练基本功”这么简单它是在练你对“数据组织方式”和“算法复杂度”的敏感度。1.2 面试官在数组题里真正想看的三件事我陪过不少人模拟面试发现同样一道数组题不同人的解法差异极大而这种差异正好暴露了面试官最在意的东西。第一是代码基本功。循环边界会不会写错索引会不会越界空数组能不能处理这些细节比算法本身更能筛人。第二是复杂度意识。题目给了10万级的数据规模你还写三重循环那不管思路对不对面试官心里已经给你打了折扣。能不能根据数据范围推断出该用O(n)还是O(n log n)的算法是很重要的能力。第三是思维升级能力。给你一道“找出数组中出现次数超过一半的元素”有人上来就排序取中位数有人用哈希表计数有人能想到摩尔投票法。三种解法没有绝对的对错但展示出的思维层次完全不同。数组题恰恰是这种“解法阶梯”体现得最明显的地方因为它的约束少发挥空间大。1.3 数据规模是藏在题目里的提示我在刷题时养成了一个习惯拿到题先看数据范围。力扣几乎每道题都会给你数组长度n或者元素值域这其实是一个非常有价值的提示。比如n在100以内那三重循环大概率能过你不需要绞尽脑汁优化。n在10万量级O(n^2)基本会超时你要想的是O(n log n)或者O(n)的做法。n在十亿这种级别连O(n)都可能不行得考虑二分或者数学公式。元素值域如果很小可以考虑计数数组、位标记这类空间换时间的手段。数据规模不是题目随便写的它是出题人给你划定的一条“复杂度红线”。熟练之后看数据范围大概就能猜出该用什么思路这能省掉很多无意义的尝试。2. 数组题四大高频考点的解题模板与代码骨架2.1 双指针对撞与快慢两种模式双指针是我在数组题里用得最多的技巧没有之一。它核心思想很简单用两个指针代替一个指针在数组中反复遍历把时间复杂度从O(n^2)降到O(n)。具体至少有两种常见形态千万别搞混。第一种是对撞指针。最常见的是有序数组里找两数之和左指针在最左右指针在最右两数之和等于目标值就返回小于目标值就把左指针往右动大于目标值就把右指针往左动。它的前提是数组有序否则对撞没有意义。// 有序数组中找两数之和返回下标 function twoSum(nums, target) { let left 0, right nums.length - 1; while (left right) { const sum nums[left] nums[right]; if (sum target) { return [left, right]; } else if (sum target) { left; } else { right--; } } return []; }这里有一个细节我要强调一下循环条件是left right而不是left right。因为数组里不能同一个元素用两次如果left right还继续判断就相当于把同一个元素重复计算了一遍。这个坑我在刚开始刷题时踩过后来每次写双指针都会条件反射地检查一下。第二种是快慢指针。典型场景是“移除数组中的某个元素”一次遍历里快指针负责扫描原数组慢指针负责指向新数组的有效位置。快指针遇到不需要删除的元素就把它复制到慢指针位置然后慢指针前进。这个做法在原数组上就地操作空间复杂度O(1)面试中很加分。// 原地移除所有等于 val 的元素返回新长度 function removeElement(nums, val) { let slow 0; for (let fast 0; fast nums.length; fast) { if (nums[fast] ! val) { nums[slow] nums[fast]; slow; } } return slow; }很多人会问为什么不直接倒序遍历删除因为JavaScript的数组删除会引起元素移动倒序删除虽然可以避免索引错乱但如果有大量删除操作性能依然不好。快慢指针在思路上更干净也更容易延伸到“去重”“移动零”这类题目上。2.2 滑动窗口从暴力嵌套到线性维护滑动窗口是数组题里另一个高频考点它解决的问题有一个共同特征需要在一个连续的子数组上做判断而这个子数组是动态伸缩的。我把它理解成一把“可以自动伸缩的尺子”。右指针负责伸长左指针负责收缩窗口内部永远维护当前符合某个条件的连续区间。最典型的问题是“和大于等于目标值的最短子数组”。// 长度最小的子数组使其和 target function minSubArrayLen(target, nums) { let left 0, sum 0, ans Infinity; for (let right 0; right nums.length; right) { sum nums[right]; while (sum target) { ans Math.min(ans, right - left 1); sum - nums[left]; left; } } return ans Infinity ? 0 : ans; }注意这里内层用的是while而不是if。为什么因为当你把左指针移动一次之后窗口内的和可能依然大于等于目标值需要继续移动左指针直到条件不再满足为止。我第一次写这个题的时候用了if结果在[2, 3, 1, 2, 4, 3]这类测试用例上直接算错后来对照调试才反应过来。还有一个滑动窗口的变种是“固定长度窗口”比如求数组里每k个连续元素的最大和。这种更简单窗口长度不变右指针每前进一格左指针也跟着前进一格。我习惯先用一个left和right都初始化为0的思路但固定窗口用for循环从k到n遍历会更直观代码也更难写错。2.3 前缀和与差分区间问题的降维武器前缀和是一个看起来很简单、但应用极广的技巧。它解决的核心问题是“频繁查询区间和”。如果不做任何预处理每次求区间[l, r]的和都要遍历一遍最坏O(n)多次查询就变成O(mn)。用前缀和数组预处理之后每次区间查询变成O(1)。构建方式不复杂新开一个长度n1的数组prepre[i]表示原数组前i个元素的和。那么区间[l, r)的和就是pre[r] - pre[l]。我建议用左闭右开区间来写能省掉很多下标偏移的思考。// 构建前缀和 const n nums.length; const pre new Array(n 1).fill(0); for (let i 0; i n; i) { pre[i 1] pre[i] nums[i]; } // 查询区间 [left, right) 的和 function rangeSum(left, right) { return pre[right] - pre[left]; }为什么pre的长度是n1而不是n因为pre[0]需要表示“前0个元素的和”也就是0这样才能处理从下标0开始的区间。这个细节如果没想清楚后面很容易出现越界或者结果偏一位的问题。差分数组则是前缀和的逆向操作更适合“对某个区间统一加同一个值”的场景。比如你要对数组的[l, r]区间整体加v直接循环可能要O(n)用差分数组只需要在diff[l] v再到diff[r 1] - v最后累加一遍恢复原数组。它常用于多次区间更新的场景如果你刷题时遇到类似“执行多次区间加操作”的题目可以往这个方向想。2.4 原地操作与状态压缩空间复杂度的取舍数组题里还有一种很“秀”的技巧就是在原数组上做标记避免额外开哈希表。它通常要求数组元素的值域和数组下标之间能建立某种映射关系。比如数组长度是n所有元素值都在[0, n-1]范围内你就可以把“数值”本身当作“下标”来用。最常见的做法是“负号标记法”遍历数组时遇到值x就把下标|x|对应的元素变成它的负数。这样不额外开空间遍历一遍就能找出重复出现过的数。不过这里有个隐患如果同一个位置被多次负号标记你还需要判断一下它是不是已经是负数。另一个常见技巧是“加上偏移量n”但容易在数据范围很大时溢出我一般不太推荐除非你有十足把握。这种原地技巧的最大风险是修改了原数组之后你再遍历时看到的不再是原始值必须用绝对值等方式还原。写这类题我会先在纸上模拟一遍确认“修改后的数据会不会影响后续判断”再动手写代码。面试时用这种技巧确实很加分因为它展示出你对空间复杂度的敏感度但如果你在高压下容易乱我还是建议老老实实开一个额外数组先保证正确性。我简单整理了一下这四种考点的适用场景方便你快速对照考点典型问题特征时间复杂度空间复杂度双指针有序性、去重、就地删除O(n)O(1)滑动窗口连续子数组、满足条件的最短/最长O(n)O(1)前缀和多次区间和查询预处理O(n)查询O(1)O(n)原地标记值域小且与下标可映射、要求O(1)空间O(n)O(1)3. 数组题的边界条件一次AC的隐藏障碍3.1 空数组与单元素数组最简单的输入最容易翻车我复盘过自己Submit失败的记录发现至少有三分之一是挂在边界输入上其中空数组和单元素数组占了大头。很多人写主逻辑时想的是“数组至少有两三个元素”结果一判断空数组就直接越界。比如你写一个求数组最大值的函数可能很自然地写出let max nums[0]然后从1开始遍历但如果数组是空的nums[0]直接就是undefined比较结果全乱套。我在刷题时习惯性在函数开头加一段边界保护if (!nums || nums.length 0) return -1; if (nums.length 1) return nums[0];这两行看似多余却能帮你挡住大量莫名其妙的问题。尤其是很多题目要求你返回下标的时候空数组和单元素数组的处理往往和正常逻辑不同必须单独考虑。还有一个建议是当你写完一个解法之后先不要提交而是自己构造几个特殊用例跑一遍。空数组、一个元素、两个相同元素、全部相同元素、升序数组、倒序数组这些用例10秒钟就能跑完但能省下你提交后反复WA的煎熬。我自己就在“两个相同元素”这种用例上翻过车因为有些去重逻辑会错误地把相同元素当作有效结果。3.2 索引越界循环边界计算的常见翻车点数组越界是新手最容易犯的错误但它又不是那种一运行就崩溃的报错很多时候JavaScript不会直接报错而是给你一个undefined让你对着结果一脸懵。我总结过几个高频越界场景。第一种是循环里同时访问nums[i]和nums[i1]比如比较相邻元素。如果你循环条件是i nums.length最后一次循环访问nums[i1]就会越界。这时候要么把条件改成i nums.length - 1要么从i 1开始遍历并访问nums[i-1]。我倾向于用后一种因为思考“当前元素和前一个元素比较”比“当前元素和后一个元素比较”更符合直觉。第二种是二分查找里的mid计算。老手都知道用left (right - left) / 2而不是(left right) / 2因为后者在语言里可能会有整数溢出问题虽然JavaScript里整数溢出风险比C小但养成好习惯没坏处。第三种是双指针移动时没有检查指针是否越过数组末尾。特别是滑动窗口里右指针已经到nums.length - 1之后你还要访问nums[right 1]。我的建议是所有从数组读取值的代码先确认下标是否在合法范围内哪怕感觉冗余。刷题时多写一行判断比调试半小时值太多。3.3 整数溢出与语言特性差异数组题里经常遇到求和、求乘积、比较大小这类运算不同语言有完全不同的表现。用C或Java刷题时int类型很容易溢出两个十亿级别的数相加直接变成负数导致比较逻辑出错。JavaScript虽然所有数字都是浮点数表示但超过安全整数范围同样会有精度问题。一个经典坑是“求数组中所有元素的和再判断”。如果数组长度很大元素值又很大结果可能超出安全范围。这时候要么提前判断是否有单个元素就超过目标值要么用更小范围的累加策略避免最后才做比较。还有一个容易被忽略的是“负数的绝对值”。在C里abs(INT_MIN)的结果可能还是负数因为正数范围装不下这么大的绝对值。数组题里你如果要用“取绝对值”来还原被负号标记的元素一定要意识到这个边界。我一般遇到这种场景会先判断元素是否大于0再决定怎么处理而不是直接取绝对值。4. 从TLE到AC数组题的时间复杂度优化实战4.1 先跑通暴力再决定要不要优化我一直不太建议大家拿到数组题就直接想最优解尤其是刚开始刷题的时候。合理节奏是先用最直白、甚至最暴力的方式把题目解出来确保思路正确再在此基础上考虑怎么优化。为什么因为如果一上来就想最优解你很可能卡在“怎么优化”上连题目本身的逻辑都没理清楚。暴力解法虽然效率差但它是很好的“参考答案”。比如“三数之和”暴力解法就是三重循环先把框架写对能过样例再去想排序双指针的优化版。这个过程能帮你理解暴力解到底慢在哪优化方向自然就清晰了。我刷题时经常先写一版O(n^2)或O(n^3)的解法拿来和最优解对比。对比之后我才会真正理解为什么需要哈希表、为什么需要排序。这种“先暴力再优化”的路径比直接看题解记忆深刻得多。4.2 根据数据规模反推目标复杂度前面说过数据范围是提示这里展开讲一下怎么用好这个提示。我自己的经验是拿到题先看n的范围心里快速定一个“必须达到”的复杂度目标。下面这个表是我刷题时经常对照的经验值不一定绝对准确但能帮你快速判断方向数据规模可接受的复杂度常用算法方向n 100O(n^3)暴力枚举、三重循环n 1000O(n^2)双重循环、两两组合、DPn 10^5O(n log n) 或 O(n)排序、二分、双指针、哈希表n 10^9O(log n) 或 O(1)二分答案、数学推导如果题目给的是n 10^5你却写出了双重循环不用跑超时也知道大概率会TLE这时候就应该停下来想想双指针或者哈希表能不能降一重循环。反过来如果n只有100你却写了一个复杂的O(n log n)解法虽然也能过但可能多花了不必要的时间。先确认复杂度目标再选择算法这是高效的刷题习惯。4.3 空间换时间哈希表是数组题的最佳外挂数组题里最经典的优化手段就是“用哈希表记录已经遍历过的信息”。拿最经典的“两数之和”来说暴力做法是两层循环时间复杂度O(n^2)。用哈希表之后只需要一次遍历每遇到一个元素nums[i]就查一下target - nums[i]在不在哈希表里如果在说明找到了另一半个组合如果不在就把当前元素作为键、下标作为值放进哈希表。// 两数之和返回满足条件的下标 function twoSum(nums, target) { const map new Map(); for (let i 0; i nums.length; i) { const need target - nums[i]; if (map.has(need)) { return [map.get(need), i]; } map.set(nums[i], i); } return []; }这个思路的本质是空间换时间用O(n)的空间把“查找”的复杂度从O(n)降到了O(1)。后面很多题目比如“最长连续序列”“和为k的子数组”其实都是这个思想的变体。哈希表在数组题里的作用类似你在一排储物柜前面放了一个索引目录每次找东西不用一个个翻直接查目录定位到对应柜子效率提升是肉眼可见的。4.4 排序预处理把无序变有序思路瞬间开阔数组题里还有一个很实用的预处理手段排序。很多问题在无序数组上很难做但一旦有序解法就变得很直观。比如前面提到的对撞双指针例子前提就是数组有序。如果题目给的是无序数组你又需要比较元素大小关系不妨先花O(n log n)的时间排序再考虑用双指针或者二分。排序看起来增加了一步操作但它往往能把一个难以下手的题目变成一个套模板就能解决的题目。我常用的判断标准是题目是否关心元素的“相对顺序”或“原始位置”。如果只关心“值”本身不关心下标排序通常是安全的如果必须返回原数组的下标排序前就要先记录原始下标排序后依然可以通过下标还原位置。比如“找出两个数使它们的和等于目标值”这类题如果要求返回原下标我会先创一个包含原始下标和值的对象数组排序后在双指针最后再根据对象里的原始下标转换结果。5. 我把数组题刷了三遍之后的复盘与节奏建议5.1 一套可复制的三阶段刷题节奏带过几个朋友刷力扣之后我总结出一套比较适合普通人的三阶段节奏尤其适合数组题专项训练。第一阶段是“手感期”大概花一周时间每天3到5道简单数组题目标不是刷量而是熟悉数组的基本操作、边界处理和常见报错。第二阶段是“题型期”用两到三周时间按我前面列的双指针、滑动窗口、前缀和、哈希表、二分查找这些高频考点分类刷中等题每天两到三道。第三阶段是“实战期”每周至少做两场45分钟限时训练题目直接从中等难度起步同时开始复刷错题。这个节奏的理念是先建立基本盘再模型化提高最后在时间压力下检验。如果你一上来就追求每天10道题前期可能会很爽但很快就会陷入“刷过就忘”的状态。数组题不是一个靠数量就能堆出效果的题型它是靠“重复和复盘”才能内化成直觉的题型。5.2 错题复盘的正确姿势很多人刷题就是提交通过之后立刻看下一题这样刷一年可能也没什么进步。我自己比较有效的做法是建立一个极简错题模板每道没一次AC的题都会记录以下几项题目考察的算法模型是什么、我第一遍的思路卡在哪个环节、最优解和我的解法差在哪里、这道题对应哪一类通法。这个模板看着麻烦但每道题只需要三四分钟就能写完。它最大的价值在于当你刷到第50道数组题时回看前30道会发现很多“卡点”其实是重复的比如“没考虑空数组”“忘记处理下标偏移”“不知道用哈希表降低查找复杂度”。把这些高频卡点整理出来之后新题再碰到类似场景会形成条件反射AC率提升非常明显。我还习惯每周末花15分钟做一次“模型打卡”把本周遇到的数组题按双指针、滑窗、前缀和等分类汇总看看哪类题型题量最多、哪类正确率最低。数组中如果某个模型的正确率特别低下一周就重点补这一类而不是漫无目的地做新题。5.3 别把“刷题量”当成核心目标我见过有人刷了300道题还是怕面试手写算法也见过只刷了一百道左右、面试时各种问都能接得上的人。差距不在数量在于有没有把题目抽象成模型。数组题尤其适合模型化因为它的题型变体相对集中很多中等难度的题都是几种基础模板的组合。比如“滑动窗口 哈希表”用于不重复字符的最长子串“排序 双指针”用于大多数和有关的数对问题“前缀和 哈希表”用于统计满足条件的子数组个数。你如果能在看到题目的时候迅速判断出“这个题是滑窗还是前缀和”就已经赢了一大半。这种判断力不是靠做新题练出来的而是靠反复总结旧题练出来的。所以我更推荐“二刷三刷”而不是“一味追新”。把做过的数组题换个问法再写一遍或者把已经AC的题改成限时重写这类“带约束的重复”比连续刷新题更能锻炼实战能力。最后说一个我自己的小习惯吧。每次拿到数组题我不急着写代码而是先盯着数据范围看十秒钟心里大概估算“暴力能不能过需不需要上哈希表、双指针、前缀和”。这个习惯让我少走了很多弯路也让我在面试时思路比大多数人快半拍。希望正在刷数组的朋友也能尝试一下说不定你的AC率也会悄悄提上去。
RELATED

相关推荐

Spring Boot连接MySQL全攻略:配置、踩坑与性能优化实战

Spring Boot连接MySQL全攻略:配置、踩坑与性能优化实战

搞Java后端的人,基本没有绕开“Spring Boot MySQL”这个组合的。我手头这个后台管理项目,从最初调研到真正上线跑稳,中间折腾了不少东西。光是把数据源配好、建几张表、CRUD跑通只是第一步;连接池参数怎么调、事务注解怎么标、时…

📅 2026/10/14 21:36:12
Runtime方法缓存cache_t源码拆解:从结构查找到扩容全解析

Runtime方法缓存cache_t源码拆解:从结构查找到扩容全解析

直接把 runtime 源码翻到cache_t这一段的时候,你会发现整个 objc4 里最性感的代码不是那些复杂的方法查找逻辑,而是这个看着只有两个字段的小结构体。方法缓存不是“顺便加的优化”,它直接决定了 ObjC 的消息发送能不能跑出接近 C 函数调用的…

📅 2026/10/14 21:36:12
SAMA V2.0:轻量级跨端状态管理契约与漂移治理方案

SAMA V2.0:轻量级跨端状态管理契约与漂移治理方案

简介:SAMA V2.0 是一款面向中初级C开发者的常用代码集合与功能扩展库,聚焦日常开发高频场景,如数据处理、文件I/O、网络通信、日志记录、XML解析、注册表操作及跨平台资源管理,显著降低重复造轮子成本。资源包共139个文件&#xf…

📅 2026/10/14 21:36:12
MORE NEWS

更多资讯

📰

小龙虾好吃的关键在技能组合:预处理到收汁全拆解

1. 做小龙虾翻车几次之后,我才意识到问题出在“技能组合”上 你可能也遇到过这种情况:照着热门菜谱买了一堆调料,锅里油也烧得冒烟了,葱姜蒜爆香的味道直往鼻子里钻,结果成品端上桌,剥开虾壳——肉是散的&a…

📰

免费SU模型网站哪里找?6个渠道+下载修模实操一次说清

做室内、建筑、景观方案阶段的朋友,估计都有过这种经历:方案明天要汇报,模型里还缺一个款式合适的沙发、一棵姿态好看的树,或者一套完整的中式隔断。这时候,“草图大师模型哪里有完全免费的下载网站”就成了最现实的搜…

📰

最大子数组和与Kadane算法:从暴力到分治,搞定LeetCode 53

最近后台访问日志里有个自称“搜索机器人”的访客连续刷了好几条警告,我一边清日志一边刷题,正好复习到题库里的第 53 题 Maximum Subarray(最大子数组和)。这道题我前前后后用四种思路解过,每次回头看都有新收获。这篇…

📰

Linux内核启动早期内存映射:idmap与swapper_pg_dir深度解析

1. 这块“内存映射”到底在干什么先从最直接的印象说起。一提到“内存映射”,很多人脑子里立刻跳出mmap、page table、缺页中断这些东西,觉得这是一套非常庞大、非常难啃的模块。但“idmap & swapper”这个标题点到的,其实是Linux内核启动…

📰

SAP HANA Merge机制与快捷登录:运维实战排查指南

很多搞SAP HANA的同事,最初都会把目光放在数据加载、备份恢复、性能调优这些大方向上,反而容易忽视两个看起来“不起眼”却在实际运维中天天要打交道的细节:一个是Merge(合并),另一个是快捷登录。这两个东西…

📰

SSE实时推送从原理到生产实践:比WebSocket更轻量

最近有个项目,需要在页面上实时展示服务端的运行状态、任务进度和告警信息。一开始想着直接用轮询,每秒钟去拉一次接口,实现简单但是心里总觉得浪费。后来查了一下,发现SSE这个方案比我印象中成熟得多——浏览器原生支持&#xff…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬