尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
LeetCode Hot100普通数组五题详解:从暴力到最优解与Web环境排查
0x3f 第24天。先报个流水账白天几乎没有写自己的代码时间全泡在“黑马web”课程上安装环境、配依赖、跑通 demo真正坐下来敲业务逻辑的时间反而没多少晚上回到 LeetCode Hot100终于把“普通数组”这一组题挨个收拾了一遍。如果你也在刷 Hot100或者正被某个 Web 课程的本地环境折腾得没脾气这篇应该能对上电波。1. 普通数组不普通Hot100分组里的这组题到底在考什么1.1 怎么理解“普通数组”这个分组LeetCode 的 Hot100 题单把题目分成了哈希、双指针、滑动窗口、子串、矩阵、链表这些标签其中有一个分组就叫“普通数组”。我第一次看到这个分组名字的时候其实挺困惑什么叫“普通”是题目简单吗不是。点进去之后我就明白了这个“普通”的意思是这些题不靠某个固定的套路标签而是需要你结合贪心、排序、模拟、原地哈希等多种思路去解。它们看起来谁都能读懂但想拿最优解必须有一点设计感。这一组题数量不多但含金量很足。我刷的时候整理了一张表先放在这里题目核心技巧目标复杂度最大子数组和动态规划 / Kadane 算法O(n)合并区间排序后依次合并O(n log n)轮转数组数组反转 / 环状替换O(n)除自身以外数组的乘积前缀积 后缀积O(n)缺失的第一个正数原地哈希 / 索引标记O(n)刷完这几道题之后我发现所谓“普通数组”其实是在考你对数组下标的利用程度。很多优化思路的本质就是把数组本身当成一个可以重复读写的临时结构空间越省题目难度反而越高。1.2 五道题的共性状态、排序与原地操作这五道题看起来毫不相干但我刷完复盘发现有三个共同点值得拿出来讲第一几乎每道题都能从“暴力解法”出发。比如最大子数组和可以双层循环枚举所有子数组合并区间可以两两检查重叠轮转数组可以每次移动一位。暴力解法写出来很快但数据量一大就全部超时。这组题真正考验的是你愿不愿意从暴力解法里站出来去找那个“多走一步就够得到”的优化方案。第二有三道题需要改变数组的“位置关系”。合并区间要排序轮转数组要反转缺失的第一个正数要让每个数字回到对应的下标上。数组本身是无序的但我们需要给它强加一个“逻辑顺序”。这种给数组建立新秩序的过程和平时搞业务数据完全不是一码事但两者在思路上是通的。第三边界条件极其恶心。空数组、全负数、重复元素、k 大于数组长度这些情况不做预处理代码大概率会在某条测试用例上翻车。我自己写的时候每道题都至少因为边界条件补了一次代码。1.3 这一组题和 Web 开发的关系有人可能会问白天学 Web晚上刷算法这不是两条线吗我自己的感受是它们并不冲突甚至离得很近。白天在“黑马web”课程里折腾的前后端数据交互本质上就是在处理数组、对象、列表这些数据结构。比如把后端的 JSON 数组经过过滤、映射后渲染到页面上和刷题时写for循环去变换数组用的都是同一套逻辑思维。更直白一点动态规划里的“状态”对应 Web 开发里的“组件状态”排序合并数据对应接口返回数据的预处理。刷题练的是微观能力Web 课程练的是宏观工程两个一起学确实累但脑子不会僵。2. 白天安了半天程序Web 课程里的真实时间黑洞2.1 为什么一个环境能折腾一下午标题里写着“安了半天程序”这里说的就是白天最大的感受很多时候我们不是在写代码而是在让代码能跑起来。我白天跟着“黑马web”课程的进度做模拟项目 X一个后台管理系统的 Demo。按理说课程给的材料已经很完整了结果卡就卡在环境上要装一类环境、配置另一类依赖再启动某个服务版本稍微对不上就报错。我把网上各种解决办法都试过一遍有的说升级到某个版本有的说降低到某个旧版本还有的直接让你删了重装。折腾到最后反而是自己冷静下来把报错信息从头到尾读了一遍才找到问题某个配置文件的版本号被写死了和新装的依赖不兼容。吃一堑长一智环境问题不是靠“别人怎么说”解决的是靠“日志怎么报”解决的。2.2 环境坑位清单与排查顺序白天踩过的坑不少趁着还记得我把自己遇到的典型问题整理成一张速查表供同样在学 Web 课程的兄弟参考现象最常见原因处理思路依赖下载慢或失败默认源在国外换国内镜像源或者配置代理仓库启动服务报端口占用上一次进程没杀干净先查端口占用进程再换端口或杀进程代码改了但页面没变化缓存/热更新失效清缓存、重启服务不要干改代码版本冲突教程用的老版本自己装成新版严格按课程的版本号来不要追求“最新”路径带中文或空格工具链解析失败项目路径统一用英文别放桌面就行我的排查顺序也分享一下先看报错的第一行日志而不是最后一行再确认依赖管理工具的源是否可用如果一切正常就清缓存重启最后才考虑搜错误码。千万不要开一个错误就复制一条命令那样很容易把环境越修越乱。3. 晚上刷题普通数组五连思路逐个拆白天把耐心都快耗完了晚上回到 Hot100 反而觉得脑子清醒不少。下面按我的刷题顺序把五道题的核心思路逐一拆开讲。3.1 最大子数组和Kadane 算法的“负收益不拿”思想题目要求找一个连续子数组使它的和最大。暴力解是枚举所有起点和终点O(n²) 复杂度。我要讲的优化思路核心其实一句话如果前面的累加和是负数那它对后面的子数组就没有任何帮助果断丢掉。用一个变量pre表示“以当前元素结尾的最大子数组和”转移方程是pre max(pre nums[i], nums[i]) ans max(ans, pre)这个方程的意思是面对nums[i]你有两个选择——要么把nums[i]接到前面的子数组后面要么干脆从nums[i]重新开始。哪个大就选哪个。举一个例子理解[-2, 1, -3, 4, -1, 2, 1, -5, 4]从1开始1比-2 1大所以这时候应该从1重新开始到-3时-2仍然大于-3说明可以保留之前的和往后看看有没有机会转正到4时-2 4 2依然小于4果断从4重新开始后面就能一路累积到4 (-1) 2 1 6。这个思路看着简单但很多人第一次写容易踩一个坑初始化ans 0结果全负数数组会返回0而正确结果应该是最大的那个负数。所以ans一定要初始化为nums[0]或者一个足够小的值。3.2 合并区间排序之后区间重叠就一句话的事儿题目给一堆形如[start, end]的区间要求把所有有重叠的区间合并。如果不排序你可能需要两两比较、反复循环很容易写乱。排序之后就清爽了把所有区间按左端点从小到大排序维护当前合并后的区间[L, R]遍历下一个区间[a, b]如果a R说明两个区间有重叠把R更新为max(R, b)否则把当前[L, R]加入答案然后让L a, R b。很多人会下意识觉得“合并区间需要判断四种重叠情况”但排序之后其实只剩一个判断新的左端点是否小于等于当前的右端点。等于也算重叠比如[1,4]和[4,5]应该合并成[1,5]。3.3 轮转数组三次反转其实是数学题向右轮转k个位置比如[1,2,3,4,5,6,7]转 3 次变成[5,6,7,1,2,3,4]。最直接的办法是用额外数组newArr[(i k) % n] nums[i]这个方法很好懂缺点是空间 O(n)。如果要做到 O(1) 空间可以用三次反转反转整个数组[7,6,5,4,3,2,1]反转前 k 个[5,6,7,4,3,2,1]反转后 n-k 个[5,6,7,1,2,3,4]第一次看到这个解法时我的反应是“这是什么神仙操作”。其实它背后的数学原因是向右轮转k次等价于把序列分成两部分然后交换这两部分并保持各自内部顺序。反转三次就是在不占用额外空间的情况下完成这个交换。一定要先执行k % n因为k可能比数组长度大比如数组长度是 7k 10实际效果和k 3完全一样。如果忘了取模反转范围可能越界或者说整个数组状态乱掉。3.4 除自身以外数组的乘积不让用除法就左右开弓这道题的限制很友好不能使用除法。如果允许除法思路就变成算总积然后除以每个元素但那样还要额外处理 0 的个数反而麻烦。标准解法分成两趟遍历第一趟从左到右用一个变量left累乘已经扫过的元素第二趟从右到左用一个变量right累乘已经扫过的元素answer[i]在左乘完的基础上再乘上right。换句话说每个位置的结果是“左边的所有元素乘积 × 右边的所有元素乘积”我们把左右两边分别算好再乘到一起。比如[1,2,3,4]先算左半部分answer [1, 1, 2, 6]再从右往左乘right依次是4, 12, 24, 24最终得到[24, 12, 8, 6]这个解法空间 O(1)不算输出数组关键是理解前缀积和后缀积是两个独立的累积过程别把两个方向的变量混在一起更新。3.5 缺失的第一个正数拿数组本身当哈希表这是普通数组里最烧脑的一道。要求 O(n) 时间、O(1) 空间找一个未排序数组里缺失的最小正整数。难点在于不能用额外哈希表却要做到哈希表的查找效果。核心思路是把数组本身改造成哈希表长度为n的数组最多能装下1 ~ n这些正数。所以我们可以让每个下标i上存放值为i 1的数字。具体做法遍历数组当nums[i]在[1, n]范围内且nums[i]应该待的位置nums[nums[i] - 1]上不是它自己时就交换交换之后当前位置可能来了一个新的合法数字所以要继续判断直到当前位置不需要再动第二次遍历时如果nums[i] ! i 1说明i 1缺失直接返回如果全对上了返回n 1。为什么不用普通的if而要用while因为一次交换只能把一个数送到属于它的位置但被换回来的数可能还是错位的。举个最简单的例子[2, 1]i 0时nums[0] 2应该和nums[1] 1交换交换后数组变成[1, 2]刚好两个数都回位。但如果数组是[2, 1, 0]交换完0位置后还要继续检查所以必须循环处理。4. 实操过程与核心环节实现可以直接抄作业版思路归思路落地到代码又是另一回事。下面我给出五道题的完整可提交代码全部基于 C注释写在我认为最值得看的地方。代码风格偏竞赛向但可读性没问题。4.1 最大子数组和参考代码class Solution { public: int maxSubArray(vectorint nums) { int pre 0; int ans nums[0]; for (int x : nums) { pre max(pre x, x); ans max(ans, pre); } return ans; } };这里有个容易被忽略的点pre初始化为 0而不是nums[0]。因为循环内会重新从x开始做一次选择所以第一次进入循环时pre x等于x结果不会受影响。4.2 合并区间参考代码class Solution { public: vectorvectorint merge(vectorvectorint intervals) { if (intervals.empty()) return {}; sort(intervals.begin(), intervals.end()); vectorvectorint ans; int L intervals[0][0], R intervals[0][1]; for (int i 1; i intervals.size(); i) { int a intervals[i][0], b intervals[i][1]; if (a R) { R max(R, b); } else { ans.push_back({L, R}); L a; R b; } } ans.push_back({L, R}); return ans; } };vectorvectorint默认排序规则是先按第一维升序第一维相同再按第二维升序刚好满足要求。最后一步一定要把最后一个区间也加进去漏掉它是最常见的错误。4.3 轮转数组参考代码class Solution { public: void rotate(vectorint nums, int k) { int n nums.size(); if (n 1) return; k % n; if (k 0) return; reverse(nums.begin(), nums.end()); reverse(nums.begin(), nums.begin() k); reverse(nums.begin() k, nums.end()); } };k % n是必须的。比如nums长度为 1k随便多大数组不会改变如果k刚好是数组长度的整数倍反转之后又转回原位等于白做所以提前返回能省一点时间。4.4 除自身以外数组的乘积参考代码class Solution { public: vectorint productExceptSelf(vectorint nums) { int n nums.size(); vectorint ans(n, 1); int left 1; for (int i 0; i n; i) { ans[i] left; left * nums[i]; } int right 1; for (int i n - 1; i 0; --i) { ans[i] * right; right * nums[i]; } return ans; } };这段代码有两个细节值得注意第一遍循环后ans[i]存的是“下标 i 左边所有元素的乘积”第二遍从右往左乘的时候ans[i]再乘上“下标 i 右边所有元素的乘积”正好得到完整结果。如果数组里有 0代码依然能正确处理因为它没有使用除法。4.5 缺失的第一个正数参考代码class Solution { public: int firstMissingPositive(vectorint nums) { int n nums.size(); for (int i 0; i n; i) { while (nums[i] 1 nums[i] n nums[nums[i] - 1] ! nums[i]) { swap(nums[i], nums[nums[i] - 1]); } } for (int i 0; i n; i) { if (nums[i] ! i 1) return i 1; } return n 1; } };while循环里有个隐藏的终止条件只有目标位置上的数不等于当前值时才会交换。如果目标位置上已经存在相同的值说明这个值是重复的交换没有意义再循环下去就是死循环。所以判断条件里的!必须带上。4.6 测试用例设计刷题只跑题目给的示例远远不够。我平时测题的标准是至少准备一组正常数据、一组边界数据、一组极端数据。这五道题我常用的用例也一并列出来题目正常用例边界用例预期结果最大子数组和[-2,1,-3,4,-1,2,1,-5,4][-1,-2,-3]6 / -1合并区间[[1,3],[2,6],[8,10],[15,18]][[1,4],[4,5]][[1,6],[8,10],[15,18]] / [[1,5]]轮转数组[1,2,3,4,5,6,7], k3[1,2], k3[5,6,7,1,2,3,4] / [2,1]除自身以外数组的乘积[1,2,3,4][0,1,2,3][24,12,8,6] / [6,0,0,0]缺失的第一个正数[3,4,-1,1][1,2,0] / [7,8,9,11,12]2 / 3 / 1特别是最大子数组那组全负数用例很多人不看题就默认答案至少是 0结果亲手写一遍就会发现正确答案是-1。5. 刷题时最容易踩的坑我的实时排查记录5.1 边界与初始化问题普通数组这一组题十个人里有九个会在边界上翻车。我把自己真实踩到的、以及身边朋友常见的几个问题整理成一张速查表症状原因解决办法最大子数组和全负数返回 0ans初始化为 0初始化为nums[0]合并区间少了一组结果最后一个区间没有 push 进答案循环结束后再 push 一次轮转数组结果不对忘记k % n先取模再判断乘积结果全为 0第一遍和第二遍的 left/right 没有分开坚持“先左后右”两个独立循环缺失正数死循环重复元素导致交换永远无法结束判断“目标位置上的值是否相同”这些错误不是不会写是写太快就容易忽略。我自己解决的办法是每道题提交前先盯着循环边界看三秒钟i是从 0 还是从 1 开始还是数组长度是n还是n - 1。5.2 超时问题暴力解到最优解的思路跃迁晚上刷题时我一开始在最大子数组和上按“暴力”的想法写了一个双循环小数据没问题一提交直接超时。后来才意识到题目给的nums长度可以到 10 的 5 次方甚至更高O(n²) 必挂。把双重循环优化成 Kadane 算法的过程本质上是减少无用功暴力解法重新计算了大量重复的子数组和而动态规划只保留“以当前元素结尾”这一种状态后面的计算就能借助前面的结果。刷这组题最大的收获不是背下来某个代码而是记住了优化前先问自己一句“上一轮的计算结果能不能被这一轮复用”5.3 Web 环境问题也在用同样的排查逻辑白天折腾“黑马web”课程环境晚上刷算法题我后来发现在排查思路上它们是一模一样的报错信息是日志测试用例是需求边界条件是环境差异。白天遇到服务启动不起来我不能盲目重装而是去看日志里第一次出现异常的位置晚上刷题超时我也不会马上改代码而是先确认是不是数据规模导致的复杂度问题。万事开头先定位动手修改要克制这条经验放到哪里都适用。6. 第24天普通数组刷完之后的几点体会白天安装配置折腾得很疲惫但晚上把普通数组五连题一张一张做掉之后我心里反而有种踏实的爽快感。刷题打卡到了第 24 天一个很明显的变化是看到题目不再慌张而是会先往下想一步——这个题暴力能解吗暴力到底浪费在哪能不能用下标和交换把空间省下来普通数组这一组的名字虽然平平无奇但题目质量相当能打。它让我把动态规划、排序、反转、前缀积、原地哈希这几种最基础的数组操作又重新练了一遍而且每一题都有“从暴力到最优”的完整升级路径。白天学的 Web 工程和晚上刷的算法题互相衬托一个管住宏观工程的思路一个锻炼微观逻辑的精度对我来说刚刚好。最后再分享一个小习惯我会在每天打卡的最后写下明天计划刷的分组比如第 25 天准备啃“矩阵”那几道题。预先定好方向第二天打开题库就不用纠结直接从任务清单里挑题开刷。你要是也在 Hot100 这条路上可以试试这个办法不用贪多一天五题保持手感比三天打鱼两天晒网强太多。
RELATED

相关推荐

智能体插槽设计:强类型能力接入接口详解

智能体插槽设计:强类型能力接入接口详解

1. 为什么“插槽”这个词突然在智能体开发圈火了? 最近在几个技术社区刷到高频词——不是“多模态”,不是“RAG”,而是“插槽”。有开发者发帖说:“给Agent加个记忆模块,结果发现框架根本不认我的类,查了半…

📅 2026/10/22 19:55:15
Nginx配置工作手册:覆盖反向代理、负载均衡与排错实战

Nginx配置工作手册:覆盖反向代理、负载均衡与排错实战

简介:面向Linux运维与后端开发初学者的Nginx系统学习资料包,围绕反向代理、负载均衡、服务集成等核心场景,以基础/进阶/架构/模块四阶段大纲串联知识点,并配有可动手验证的配置案例与Nginx启动流程、HTTP配置块解析图,…

📅 2026/10/22 19:55:15
VS680龙虾盒子实测:插电即用的边缘AI推理终端

VS680龙虾盒子实测:插电即用的边缘AI推理终端

1. 先说结论:这玩意儿解决的到底是什么问题VS680龙虾盒子,单看名字你可能觉得它就是个带语音助手的智能音箱,或者又是什么"AI玩具"。我第一次拿到手的时候也这么想,毕竟"盒子"类产品见太多了,无非…

📅 2026/10/22 19:55:15
MORE NEWS

更多资讯

📰

基于SpringBoot+Vue的养老公寓管理系统:企业级架构与健康预警闭环解析

做了几年医疗健康类信息化项目,养老公寓管理系统这套东西一直是我觉得“看着简单,做起来全是细节”的典型。很多刚入行的朋友一听到“养老公寓管理系统”,第一反应就是“不就是个带老人信息管理的CRUD吗”。真拿下一个企业级项目的时候&#…

📰

反向传播与自动微分:PyTorch中梯度计算的原理与实践

1. 从链式法则到反向传播:为什么这一步必须学扎实 做深度学习也好几年了,带过不少新人。每次讲到 Back Propagation(反向传播)这一节,我都要专门停下来,因为这是一个分水岭——懂不懂反向传播,直…

📰

LangGraph实战:用图编排搞定复杂智能体流程

做应用层的朋友应该都有同感:模型本身往往不是最难的,真正难的是把模型塞进一条会转弯、会分叉、还会重复执行的业务流水线里。我帮某电商业务搭智能客服时,需求其实很朴素——用户进来先判断意图,查订单的走查询工具,…

📰

学术速递信息筛选与论文解读实战:从信息源到日更体系

1. 一份学术速递背后的信息筛选逻辑每天早上八点半,我习惯性地打开几个固定的信息源,扫一遍过去24小时内冒出来的新论文。这个动作从读研那会儿就开始了,到现在做了快十年,已经变成了一种肌肉记忆。2月23日这一期的学术速递&#…

📰

自动驾驶基座模型:从多模态感知到决策能力升级

前阵子海外GPU技术峰会刚结束,朋友圈几乎被一个词刷了屏:基座模型。尤其是一家长期专注L4级自动驾驶的公司,趁峰会窗口放了基座模型的新进展,并且把“自动驾驶”和“AI能力体系”放进同一个叙事框架。这组合乍看起来有点反差——一…

📰

Spring AI Alibaba停更后,Java AI应用开发的工程化选型与落地实践

1. 一个停更消息引发的技术选型焦虑前几天在技术群里看到有人转了一张截图,说 Spring AI Alibaba 这个项目停更了,紧接着群里就炸了锅。有人感叹“Java 在 AI 这条路上果然走不通”,有人开始盘算着要不要转 Python,还有人直接甩出…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

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

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

📞 💬