资讯详情

资讯详情

代码随想录刷题Week1:数组与链表13题核心总结

这周把代码随想录的刷题计划正式启动起来了。Week1的目标很清晰——数组和链表这两个最基础的数据结构专题一共刷了13道核心题其中数组6道、链表7道每天固定上午9点到11点两小时周日用半小时做周复盘。这篇文章把这一周的实际经历、踩坑记录、每道题的破题思路全部整理出来给同样在跟代码随想录路线刷题的同学一个参考。先说结论按专题刷题比按题号刷题效率高得多第一周把地基打牢后面碰到哈希表、二叉树、动态规划时才不会慌。1. 为什么第一周先把数组和链表打穿1.1 刷题计划怎么定代码随想录的专题顺序不是随便排的数组和链表排在前面是因为这两个结构是后续所有算法专题的载体。哈希表底层依赖数组和链表栈和队列的实现离不开它们二叉树用链式存储图论里的邻接表也是链表思想的延伸。第一周不把这两个基础打牢后面等于在沙滩上盖楼。我给自己定的原则是每天两道新题周末不刷新题只做本周错题和重点题的二刷。这个节奏不算快但目标是每道题都能独立讲清楚思路、边界条件和复杂度而不是做完了事。事实也证明贪多求快在刷题这件事上几乎总是适得其反第二周回头看第一周的题能留下深刻印象的恰恰是那些当天花时间抠过细节的题目。计划定下来之后我按代码随想录的目录把题目清单列成了一张表数组专题的二分查找、移除元素、有序数组的平方、长度最小的子数组、螺旋矩阵II链表专题的移除链表元素、设计链表、反转链表、两两交换链表中的节点、删除链表的倒数第N个节点、链表相交、环形链表II。清单列出来后每天的任务就非常明确不需要临时想今天刷什么决策成本降到最低。1.2 按专题刷和按题号刷的区别以前我刷题是按题号从前往后刷结果就是今天碰一道二叉树明天碰一道动态规划知识点完全没有连贯性。每道题都要重新理解数据结构本身等于每次都在做冷启动。代码随想录按专题组织的思路完全不同一周内反复操作同一种数据结构、反复使用同一种技巧肌肉记忆就是这么形成的。举个具体的例子螺旋矩阵II和有序数组的平方看起来差异很大但它们都在反复训练“边界收缩”和“指针移动”这两个核心动作。链表专题里的移除链表元素、设计链表、反转链表本质上是同一套指针操作的三种变化。当你能在一周内把同一类操作重复十几次这些技巧就不再需要死记硬背而是变成条件反射。还有一个容易被忽略的好处专题刷题能让你快速识别“这道题在考什么”。第二周刷哈希表时遇到需要判断元素是否重复的题我立刻能联想到数组专题里的双指针思路这种迁移能力只有在同类题集中训练之后才会出现。2. 数组专题核心题目拆解2.1 二分查找边界条件一次写对的套路二分查找这题看似简单却是很多人面试翻车的起点。代码随想录反复强调一个核心问题区间的定义决定了代码怎么写。我用的是左闭右闭区间也就是left初始化为0right初始化为数组长度减1这个区间定义下循环条件必须是while (left right)因为当left right时区间里还有一个元素需要判断。int search(vectorint nums, int target) { int left 0, right nums.size() - 1; // 左闭右闭 while (left right) { int mid left (right - left) / 2; // 防止溢出 if (nums[mid] target) return mid; else if (nums[mid] target) left mid 1; else right mid - 1; } return -1; }这里有两个细节值得多说一句。第一mid的计算写成left (right - left) / 2而不是(left right) / 2是为了防止两个大数相加时溢出这个写法在所有二分场景下都适用。第二当nums[mid] target时left更新为mid 1而不是mid因为mid位置的元素已经被比较过了肯定不是目标值保留它会造成死循环。这两个点我在第一周踩了个结实后面写任何二分变体题目都格外注意。2.2 移除元素与有序数组平方双指针的两种姿势移除元素要求原地删除数组中所有等于目标值的元素不能申请额外空间。最直观的想法是每遇到一个目标值就把后面所有元素往前移时间复杂度O(n²)数据量一大必挂。代码随想录给出的解法是快慢指针快指针负责遍历整个数组找“需要保留的元素”慢指针负责记录当前新数组的末尾位置。int removeElement(vectorint nums, int val) { int slow 0; for (int fast 0; fast nums.size(); fast) { if (nums[fast] ! val) { nums[slow] nums[fast]; } } return slow; }这个解法的精妙之处在于它把“删除”变成了“覆盖”快指针找到的合法元素依次往前放慢指针就是新数组的长度。我一开始没绕过弯来总觉得删除应该把元素清掉实际上数组的“长度”只是一个逻辑概念返回值本身就是长度后面的脏数据根本不用管。有序数组的平方这题同样用双指针但思路从“同向移动”变成了“相向移动”。原数组是非递减排序平方之后最大值一定在两端所以两个指针一头一尾比较平方大小把大的放进结果数组的末尾谁大谁往中间移动。这题让我意识到双指针不是固定的模板而是根据数组特性灵活选择移动方向。2.3 长度最小的子数组与螺旋矩阵滑动窗口和模拟长度最小的子数组是滑动窗口的入门题要求在数组里找一个连续子数组使其和大于等于目标值并且长度最小。窗口的右边界负责扩大和左边界负责在满足条件时收缩寻找更短区间。int minSubArrayLen(int target, vectorint nums) { int left 0, sum 0, result INT_MAX; for (int right 0; right nums.size(); right) { sum nums[right]; while (sum target) { result min(result, right - left 1); sum - nums[left]; } } return result INT_MAX ? 0 : result; }这段代码的核心是while循环里的收缩逻辑。每次右边界扩展后只要当前窗口满足条件就尝试移动左边界缩小窗口并更新最小长度。我写这题时犯过一个典型错误把while写成了if导致窗口收缩不彻底结果不是最优解。滑动窗口的关键就在于窗口的收缩必须用循环反复进行直到条件刚好不再满足。螺旋矩阵II是数组专题里唯一一道“纯模拟”的题没有算法技巧就是考察循环控制。它的核心是维护四个边界上、下、左、右每填完一圈就收缩一个边界。我一开始没注意到n为奇数时最中间会剩一个格子导致结果数组中间留了个0。这个问题的解决办法是在循环结束后单独判断n是否奇数如果是就手动填中间格。3. 链表专题核心题目拆解3.1 虚拟头节点一个让链表操作统一的小技巧链表操作最烦人的地方在于删除头节点的逻辑和删除中间节点不一致。中间节点的删除需要让前一个节点的指针指向当前节点的下一个节点但头节点没有前驱只能单独处理。代码随想录给出的方案是自己创建一个虚拟头节点让它指向真正的头节点这样所有节点都变成了“有前驱”的普通节点。ListNode* removeElements(ListNode* head, int val) { ListNode* dummy new ListNode(0); dummy-next head; ListNode* cur dummy; while (cur-next ! NULL) { if (cur-next-val val) { cur-next cur-next-next; } else { cur cur-next; } } return dummy-next; }注意这一段代码的判断逻辑当发现cur-next-val等于目标值时执行cur-next cur-next-next此时cur不允许向前移动因为新的cur-next可能仍然是目标值只有当当前节点的下一个不是目标值时cur才往前走。这个“删除后不前进”的细节我一开始没注意导致连续两个目标值时第二个删不掉。设计链表这道题是虚拟头节点的集大成者需要实现get、addAtHead、addAtTail、addAtIndex、deleteAtIndex五个操作。我写的时候把size变量漏了结果get和deleteAtIndex里做下标合法性判断时需要不断遍历计数代码又乱又容易错。后来给链表类加了一个size成员每次增删都维护它所有操作的边界判断瞬间清爽了。3.2 反转链表与两两交换指针怎么挪才不乱反转链表是链表专题里最经典的题解法有迭代和递归两种。我推荐优先把迭代双指针法练熟因为它思路直观也不容易爆栈。ListNode* reverseList(ListNode* head) { ListNode* pre nullptr; ListNode* cur head; while (cur ! nullptr) { ListNode* next cur-next; // 先保存下一个节点 cur-next pre; // 指向前一个 pre cur; // pre 前进 cur next; // cur 前进 } return pre; }这段代码最关键的是一开始那行ListNode* next cur-next;如果不先保存下一个节点cur-next pre执行之后原来的下一个节点就找不到了。我第一遍写的时候忘了这行运行直接跑飞。这个“改指针前先保存后续节点”的习惯在整个链表专题里会用无数次。两两交换链表中的节点比反转链表更考验指针操作的顺序。我的做法是准备三个指针pre指向两个待交换节点的前一个cur指向第一个待交换节点next_指向第二个待交换节点。先让pre-next指向cur-next再让cur-next指向next_-next最后让next_-next指向cur然后pre移动到cur位置继续下一轮。写代码时要特别注意循环条件链表长度为偶数时pre-next不为空奇数时pre-next-next为空两种情况都要能正确退出。3.3 链表相交与环形链表快慢指针的进阶用法链表相交那题暴力解法是两层循环时间O(n×m)代码随想录给出的高效思路是让两个链表的尾部对齐。具体做法是分别遍历两个链表得到长度然后让长链表的指针先走差值步接着两个指针同步前进第一个相遇的节点就是交点。环形链表II是快慢指针的经典应用。快指针每次走两步慢指针每次走一步如果链表有环两个指针必然在环内相遇。这里有一个关键的数学结论从头节点到环入口的距离恰好等于从相遇点到环入口的距离。所以当快慢指针相遇后把其中一个指针重置到头节点两个指针都改为每次走一步再次相遇的位置就是环入口。我第一次看这个结论时觉得很神奇代码随想录的文章里给出了完整的推导过程。理解推导的价值在于面试时如果被追问“为什么这样能找到入口”你能讲清楚而不是背代码。这一周我把推导过程抄在笔记里第二周复习时再看已经能自己复述整个逻辑了。4. 刷题节奏与复盘方法4.1 一道题该花多长时间很多刷题新手的问题不是不努力而是死磕太久。一道题想四五个小时还写不出来挫败感极强第二天就不想碰了。我给自己定的规则是拿到一道题先独立思考15分钟15分钟内没有任何思路直接看代码随想录的题解如果15分钟内有思路但写不出来最多再给自己15分钟尝试之后也去看题解。这个时间盒子的策略让我一周下来保持了稳定的刷题热情。看题解不是抄代码重点是理解它的思路然后合上答案自己独立写一遍。如果独立写的过程中卡住了再回来看题解找到卡住的那个点并且在笔记里标记出来方便复习时重点关注。这个“卡点标记法”在后续复习阶段特别有用它能直接告诉你哪些地方是真正没吃透的。4.2 题解笔记记什么我的笔记不是抄一遍标准答案而是记录三类内容第一这道题的核心思路用两三句话说清楚第二我一开始哪里没想到边界条件或者空间复杂度的坑在哪里第三复杂度分析时间、空间都得写清楚。比如移除元素那题我记的是“快慢指针慢指针指向新数组末尾快指针找合法元素删除本质是覆盖”同时标注“空间O(1)时间O(n)”。笔记还有一个作用就是二刷时的起点。第一周周日复盘时我只看笔记不看代码尝试回忆每道题的思路和代码结构回忆不出来的就是需要重点复习的题。实测下来13道题里有3道在周复盘时思路模糊周日当天全部二刷了一遍。如果当时没记笔记这3道题可能就稀里糊涂地过去了后面遇到变体题大概率又得从头学。另外我还会在笔记里记录每道题的复杂度分析思路。数组题的复杂度一般好判断链表题要注意递归解法可能产生O(n)的栈空间。这些分析习惯一开始觉得麻烦但第二周做哈希表专题时我已经能比较自然地分析复杂度和空间取舍了。5. 本周高频问题与避坑清单5.1 运行报错类问题第一周写代码最常见的报错集中在数组越界和空指针访问上。数组越界主要发生在二分查找的right nums.size() - 1没考虑数组为空的情况以及滑动窗口里nums[left]的left可能超过right。链表更容易出问题访问cur-next-next之前必须先确认cur-next不为空否则直接崩溃。这类问题其实有一个共同的预防手段每次访问指针或数组下标之前先问自己一句“这一步的边界条件明确了没有”。我在代码随想录的文章里学到一句话链表操作中所有“先访问再判断”都是危险的安全顺序永远是“先判断后访问”。这个习惯养成之后链表题的通过率高了一大截。5.2 思路卡壳类问题这周我卡壳最严重的是环形链表II的入口推导以及设计链表里的size维护两个问题性质完全不同。环形链表是数学推导没理解反复看了两遍推导过程才看懂设计链表是自己把简单问题复杂化了一开始没用size变量导致每次都要遍历整个链表来确认边界。这两种卡壳放在一起有个共同启示遇到卡壳要先判断是“原理没懂”还是“工具没用对”对症下药才有效率。我还总结了一个处理卡壳的小技巧卡壳时先把代码中所有变量的含义写清楚注释写完整很多问题在这一步就能暴露出来。比如两两交换链表的题我一开始把pre、cur、next_三个变量的语义想混了注释写完后发现pre的位置更新逻辑错了整个思路瞬间清晰。5.3 复杂度分析混乱怎么破第一周刷题时复杂度分析最容易出错的场景是递归类操作和嵌套循环。反转链表的递归解法空间复杂度是O(n)因为递归调用栈深度等于链表长度但我一开始总记成O(1)。成对出现的内层循环也需要仔细数不是看到两个循环就一定是O(n²)要看内外层循环是否有独立的边界变量。我的做法是每道题写完代码后强制自己在笔记里写一行复杂度分析想不出来就翻资料查明白再记。这个过程不能跳过因为复杂度分析能力是面试一等一的考核点代码写得再对说不出复杂度也拿不到分。第二周再做哈希表和字符串专题时我已经能比较自然地估算时间复杂度和空间复杂度了这跟第一周的刻意训练直接相关。6. 下周计划与本周真实体会6.1 下周进入哈希表与字符串专题按照代码随想录的路线Week2进入哈希表和字符串。哈希表专题会用到数组、Set、Map三种底层结构正好检验第一周数组基础打得牢不牢字符串专题则会有很多原地修改和双指针的应用场景。我给自己定的目标是哈希表9道题、字符串5道题周六照例复盘周日上午预留时间二刷本周标记出来的重点题。6.2 一周下来最值得说的三个体会第一个体会是按专题刷题真的能让思路“串成串”。第一周刚开始时每道题对我来说都是孤立的但刷到链表相交和环形链表时我突然意识到它们底层都是快慢指针和链表遍历的不同组合这种融会贯通的感觉是不会出现在按题号乱刷的模式里的。第二个体会是固定时间刷题比随机时间刷题更容易坚持。我每天上午9点到11点刷题形成规律后到点就会自然进入状态不需要额外消耗意志力。第三个体会是题解笔记不是负担是给自己留的复习材料。第一周写笔记时觉得浪费时间周复盘时才发现没有笔记的复习毫无方向。最后分享一个这周踩过的坑我第一天为了把二分查找的边界彻底搞懂一道题刷了两小时中间一度怀疑自己是不是理解能力有问题。后来发现二分查找的边界问题本质上是区间定义问题只有两种写法各练熟一种其他变体的边界判断都从定义推导根本不需要死记“加一还是减一”。如果你第一周也在边界条件的泥潭里挣扎先把开闭区间定下来再写代码思路会清楚得多。第一周只是开始按这个节奏走下去三个月后回头再看这些题你会发现一切都是值得的。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →