刷题第六天不放弃:四道基础算法题与英文题面精读复盘
刷算法题这件事大多数人的第一次放弃基本都发生在第六天前后。为什么偏偏是第六天因为前三天靠新鲜感驱动第四五天开始尝到一点我能解出来的甜头等到了第六天新鲜感消耗得差不多难度曲线又悄悄抬头——前两天照着套路就能写的题这天开始要多想几层人就会突然怀疑自己是不是这块料。我这些年参与过好几轮算法训练计划也带过新人第六天的掉队率肉眼可见比前两天高。所以这篇复盘我不想讲什么高深知识就原原本本记录第六天做的两件事练四道基础算法题再完成一份计算机英语翻译。如果你也正在一个需要天天打卡的训练周期里或者正准备刷LeetCode必刷的基础算法题这份记录应该比那些三十天通关的口号实用得多——第六天能不能稳稳走过去直接决定后面二十多天的节奏。1. 第六天为什么我把基础题和英语翻译绑在一起1.1 第六天是刷题计划里最容易放弃的一天先说一个反常识的观察大多数人制订刷题计划时最大的敌人不是难度而是间歇性自我怀疑。第一天打鸡血第二天踌躇满志第三四天开始出现小成果到第五天晚上心里往往冒出一句明天要不歇一天。第六天早上打开题库如果看到一道题不是一眼能看懂这句歇一天就会瞬间变成我不适合学算法。所以第六天的选题策略很关键不能太简单太简单会无聊会觉得在浪费时间也不能太难太难会直接把信心击穿。最好选那种基础但需要认真想一下的题目——你做的时候确实在动脑做完之后又觉得自己完全能掌握。这种微妙的成就感恰恰是撑过第六天的心理燃料。我自己的经验是第六天完成四道基础题并写下复盘的人绝大多数能撑到第二周结束而那天随便刷几道难度波动大的题、不做任何整理的人往往在第七八天就悄悄停更了。这跟意志力关系不大纯粹是正反馈断了。1.2 算法题与计算机英语互相支撑的双主线表面上看算法题和计算机英语翻译是两条学习线一个练逻辑一个背单词。但真正实操时会发现两者底层高度重合算法题需要你精确理解题面的约束条件而计算机英语翻译练的恰恰就是精确理解技术文本中的限定关系。举个最简单的例子题目里出现in-place这个词如果你只知道它字面意思原地不知道它在算法语境里意味着不能借助额外数据结构、只能用 O(1) 额外空间那代码大概率会写歪。再比如non-decreasing order很多英语基础不错的人也容易翻成非递减顺序却没意识到它真正的含义是允许相等元素的递增序。这种精细理解靠背单词表是学不来的只有在刷题时拿真实题面去磨才能磨出来。反过来说如果只背单词不做题那些术语没有上下文支撑三天就忘。我现在带的训练节奏从第六天开始会把翻译任务题面化——不翻译泛泛的技术文章直接翻译当天要做的四道题的英文原题。一行题面就是一道阅读理解既练了英文又强迫你直面题目本来的样子。2. 基础盘面第六天这四道题是怎么选出来的2.1 选基础题的三条硬约束第六天选什么题我不是随手翻题库而是给自己设了三条硬约束。第一条数据结构的覆盖面不能重叠。今天不能四道题全是数组那样练的是重复劳动。我要求四道题覆盖至少四种常用手法让数组、哈希表、栈、双指针轮流上场。第二条每道题背后必须有一个能迁移出去的核心手法。所谓基础题价值不在题本身而在它浓缩的那个套路。比如两数之和背后的哈希补数法之后遇到三数之和、四数之和、以及一堆找配对类问题都能复用有效的括号背后的栈结构之后处理编译器括号配对、解析路径、编辑器撤销逻辑都是同一套思想。如果一道题做完只能对付它自己那它就不是好题。第三条题目难度必须稳定在基础档且当天能写完代码加英文精读。这里我多说一句很多人对基础档有误解以为基础就是简单题。其实基础档指的是核心环节是基础手法的题——合并两个有序数组在 LeetCode 上是简单题但它用到的逆向双指针归并思想很多中等题都在内部复刻。2.2 第六天四道题清单与核心考点以下是我第六天实际执行的清单先把题号摆出来方便你直接对照练习英文题名中文题名核心考点Python 主要结构Two Sum两数之和哈希表、空间换时间dictReverse String反转字符串双指针、原地修改list、左右指针Valid Parentheses有效的括号栈、最近匹配list 模拟栈Merge Sorted Array合并两个有序数组逆向归并、指针移动list、三指针这四道题为什么是它们而不是动态规划也不是二叉树因为第六天的目标是稳住基本盘数据结构的糖葫芦还没吃到树和图那一串。二叉树题目再简单也需要先理解节点指针和递归遍历的概念这一天引入就是给自己添堵动态规划更是完全不同的一种思维方式放到第二周以后再说更合适。另外我也在这天刻意避开了反转链表。原因是反转字符串作为双指针入门更线性直观索引之间的关系一眼能看清适合作为原地修改这个概念的最小样本。链表版的双指针反转虽然也经典但对第六天来说先把内存中的索引关系吃透隔一周再去碰指针操作反而更顺。3. 逐题拆解Python 解法与思路全过程3.1 两数之和空间换时间的最小范本两数之和是所有哈希表题目的出发点题面简洁到让人容易轻视给定一个整数数组nums和一个整数目标值target找出数组中两个数使它们的和等于target返回这两个数的下标。绝大多数人看到这道题脑子里会出现最直觉的双重循环外层固定一个数内层找有没有另一个数能凑成target。这种暴力解法能过但时间复杂度是 O(n^2)。第六天起步的时候我建议你仍然先把暴力解写出来——不是浪费时间而是让自己看清两层循环里其实做了大量重复查找这件事。然后才是哈希表解法每遍历到一个数就去查目标值减当前数complement是否已经出现过。把已经见过的数放进字典键存数本身值存下标。这样查找从 O(n) 降为 O(1)总复杂度降到 O(n)。空间复杂度上升到 O(n)换来的时间收益非常显著。def two_sum(nums, target): seen {} for i, num in enumerate(nums): complement target - num if complement in seen: return [seen[complement], i] seen[num] i return []这里有一个细节我反复提醒新人顺序很重要。必须先判断补数在不在seen里再把当前数存进去。如果反过来先存后查当complement恰好等于当前数时就会返回同一个数的下标两次这在 LeetCode 上属于边界错误。你自查代码的时候先把这个顺序刻进脑子里。我踩过的另一个坑是下意识写return []但题目保证一定有解多写这行其实问题不大真正的问题是有时会漏掉enumerate的i而多写一行索引自增。这些细节单独看都是小事攒多了就变成明明会做却总不过的挫败感。3.2 反转字符串双指针如何做到不用脑子反转字符串这道题题面上清清楚楚写着不要给另外的数组分配额外的空间必须原地修改输入数组、使用 O(1) 的额外空间解决。看到这几句话你心里应该立刻拉响警报——不能用切片。我知道很多 Python 新手会写s s[::-1]乍一看结果对但这一步其实创建了一个新列表然后让变量指向新对象完全没有在原列表上做修改。题目要求的是原地操作LeetCode 判题时看的是原参数s有没有被改动。所以第六天练这道题练的不是反转本身而是在限定条件下修改数据的意识。双指针解法非常朴素一左一右往中间走交换两边字符直到左右指针相遇或交错。def reverse_string(s): left, right 0, len(s) - 1 while left right: s[left], s[right] s[right], s[left] left 1 right - 1这里值得展开说两点。第一Python 的多重赋值s[left], s[right] s[right], s[left]是同时计算右边的值再赋值等价于引入临时变量比手写临时变量干净。第二边界条件可以放心交给left right数组长度为奇数时中间那个字符不用动偶数时所有字符两两交换完毕。这个写法比for i in range(len(s)//2)更不容易出错因为索引天然对称不用自己去换算right len(s) - i - 1。从英语学习角度这道题里最重要的词就是in-place。我见过不少人看到这个词翻成在合适的位置然后完全理解偏。它在算法里的精确含义是对原始数据结构进行修改不依赖额外空间以后你在很多链表、数组题里都会见到只要题干出现这个词第一反应就应该是双指针或原地交换这一挂的手法。3.3 有效的括号栈解决最近匹配有效的括号是栈结构的入门标配题面会给一个只包含()[]{}的字符串要求判断括号是否成对匹配且顺序正确。为什么谈栈因为括号匹配的本质是最近关联——最后一个出现的左括号必须被第一个遇到的右括号所匹配。生活化类比是叠盘子你只能拿到最顶上那个盘子想要取出中间的盘子得先挪开上面的。括号字符串也是一样读到一个右括号时它需要去匹配的是最近未匹配的那一个左括号这正是栈后进先出的天然行为。def is_valid(s): stack [] mapping {): (, ]: [, }: {} for ch in s: if ch in mapping: top stack.pop() if stack else # if mapping[ch] ! top: return False else: stack.append(ch) return len(stack) 0这道题的思考有两个关键点。第一个是判断当前字符是左括号还是右括号这里用if ch in mapping判断的是当前字符是否作为右括号存在于字典的键里如果是右括号就弹出栈顶进行匹配如果是左括号入栈。第二个是弹栈前必须先判断栈是否为空——如果来了一个右括号栈里什么都没有说明多了右括号直接False。很多解法喜欢用elif ch in (这样的方式来判断左括号再手动配对我倾向于用字典反转映射好处是后面想扩展场景比如加一种新的括号时只需要改mapping。从第六天的角度我希望你建立的习惯是先用数据结构把逻辑理清再考虑代码怎么写。3.4 合并两个有序数组从后往前写一次过合并两个有序数组这道题细节比看起来多得多。题目给两个有序数组nums1和nums2nums1的有效长度是m数组实际容量是m n需要把nums2合并进nums1且不能另开新数组。很多人第一次做这道题第一反应是从前往后合并就像合并两个有序链表那样。但数组和链表有个本质不同数组在中间插入元素需要整体移动你把nums1[0]改成nums2[0]之后后面所有有效元素都没了。所以标准解法是逆向操作从nums1的末尾空位置开始往前填。def merge(nums1, m, nums2, n): p1 m - 1 p2 n - 1 p m n - 1 while p2 0: if p1 0 and nums1[p1] nums2[p2]: nums1[p] nums1[p1] p1 - 1 else: nums1[p] nums2[p2] p2 - 1 p - 1这段代码里三个指针的分工很清楚p1指向nums1有效区末尾p2指向nums2末尾p指向nums1合并后区域的末尾。每次比较p1和p2指向的元素谁大放谁到p然后指针前移。为什么要判断p1 0因为可能出现p1已经走完、但p2还有剩的情况此时直接把nums2剩余部分填入nums1即可反过来p2走完就结束因为nums1剩下的元素已经在正确位置上不需要动。这个谁先耗尽的条件判断就是这道题最容易出错的边界。我当年第一次做这道题时习惯性地开了一个res []然后往里面放排序结果最后才发现题目要求的是原数组nums1变更等于白写了一版。现在回看这道题最好的收获不是我合并了一个数组而是理解从后往前遍历这个技巧在数组原地修改中的价值——它同样能解决很多删除重复元素、移动零这类问题。4. 计算机英语翻译把英文题面榨干4.1 边刷题边学英语比单独背单词高效在哪很多人问我为什么要边刷题边搞计算机英语翻译直接背一份技术词汇表不就行了我实测下来的结论是词汇表适合复习不适合初学。纯背单词你记住的是中文释义而不是这个词真正约束了什么。题面里的must不是普通的必须它在算法描述里代表一条硬性约束违背它程序就跑不出正确结果until也不是单纯的直到它在很多题里划定了循环的中止条件。这些语义只有在真实题面里碰到才有体感。具体操作上我的方法是每天只处理两道题的英文原题不求多但求每个词都抠干净。拿当天要做的题从第一句话开始逐句翻译成中文然后对照题目中的代码示例验证自己的理解。翻译不是把单词替换成中文而是把限定关系翻出来——比如such that后面跟的是结果约束given后面跟的是输入定义without extra space是操作约束。把这几个连接词理清楚英文题面等于一张详细的产品需求文档。4.2 两段英文原题的逐句精读第六天我精读了两段原文一段是 Two Sum 题干一段是 Valid Parentheses 题干。先说 Two SumGiven an array of integersnumsand an integertarget, return indices of the two numbers such that they add up totarget.逐句拆开看英文中文理解需要留意的点Given an array of integersnums给定一个整数数组numsarray of integers是固定搭配表示整数数组and an integertarget以及一个整数target变量名直接保留return indices of the two numbers返回两个数的下标indices是 index 的复数这里明确要求返回下标不是返回值本身such that they add up totarget使得这两个数相加等于targetsuch that引导的是约束条件表明返回结果必须满足这个关系这段题面里最容易被忽略的是indices这个复数形式。很多新手看到 indices 就懵以为是个特殊的索引结构其实它就是 index 的复数。同样常见的还有e.g.例如、i.e.也就是前者后面跟例子后者后面跟同义解释搞混了会把题面的定义理解偏。再看 Valid Parentheses 稍微长一点的题干Given a stringscontaining just the characters(,),{,},[and], determine if the input string is valid. An input string is valid if: 1. Open brackets must be closed by the same type of brackets. 2. Open brackets must be closed in the correct order.第一句里的containing just the characters不是仅仅包含这些字符这么简单它在定义输入域暗示你不需要考虑其他类型字符的干扰。第二句里的determine if翻译过来是判断是否这里if引导的是判断条件不是普通英语里的如果。后面两个must也很关键第一个must说明同类型闭合是硬要求第二个must说明顺序正确是硬要求。两个条件同时满足题面里的 valid 才成立。4.3 一页纸整理那些容易翻错的计算机术语第六天边做题边积累我整理了一张高频术语对照表。这张表是我踩过不少坑之后总结出来的特别适合放进自己的复习笔记里英文术语正确含义容易误翻的点in-place原地修改不使用额外空间不是在合适的位置而是直接在原数据上操作iterate遍历、迭代不是重复做某事而是逐个处理元素keep track of记录、维护某个状态字面直译成保持追踪会绕其实就是用变量保存中间结果non-decreasing order非递减顺序允许相等很多人理解成严格递增实际上相等的数也允许存在constraints数据范围、限制条件不只是限制它在题面里通常指输入大小等说明edge case边界情况、极端输入不是边缘情形在算法里指空输入、单元素、最大区间等情况do not allocate extra space不要分配额外空间等于只允许 O(1) 额外空间的隐式约束我的使用方法是每遇到一个新术语先在表格里查一次然后回到原题里用一句话造句。比如把keep track of放到我们用哈希表记录每个元素是否出现过这个实际场景里第二天还能回想起来。这道工序看起来碎但累积一周后再看英文技术文档障碍会小很多。5. 第六天实操复盘的避坑心法5.1 三个会让计划原地解散的坑字面意义上的避坑我不展开我想聊的是三个会让整个刷题计划原地解散的坑。第一个坑是看完题解就觉得自己会了。第六天是最容易中招的时间点——因为前面几天刚学会几个套路看到一道新题时会习惯性地打开题解扫一眼思路然后哦原来是这么回事关掉页面就觉得自己掌握了。结果是第二天遇到同一套路的不同变形照样卡壳。我的破解方法是看题解之前必须先逼自己写出暴力解哪怕超时都先自己写一版。有了自己思考的过程再看题解才知道别人在哪一步做了优化而不是只记住结论。第二个坑是只刷题不复盘。刷题打卡最容易变成流水账——今天做了十道题明天做了十道题每周回顾时却发现自己只是把会做的题重复了七遍不会的题始终没补上。第六天正好是复盘习惯的养成窗口强制记录每道题的考点、复杂度、卡点比多刷五道题值钱得多。第三个坑是英语翻译只看不写。眼睛看到某个术语感觉懂了和能准确写出来完全是两回事。第六天以后我要求自己每道题至少摘录三个英文关键词并强制在注释里写上中文解释。身体一旦动起来记忆锚点就多了。5.2 可以直接抄的每日复盘模板刷到第六天我开始用统一的模板做每日复盘。这个模板不依赖特定工具你用备忘录、Notion、语雀甚至纸笔都行。核心是每项都要写哪怕写得很短日期第 6 天 今日题目 1. Two Sum / 两数之和 - 考点哈希表、空间换时间 - 复杂度O(n) / O(n) - 英文关键词indices, complement, iterate - 卡点先查表还是先存表的顺序问题 2. Reverse String / 反转字符串 - 考点双指针、原地修改 - 复杂度O(n) / O(1) - 英文关键词in-place, modify the input array - 卡点切片解法违反空间约束 3. Valid Parentheses / 有效的括号 - 考点栈、最近匹配 - 复杂度O(n) / O(n) - 英文关键词stack, open brackets, correct order - 卡点弹栈前忘记判空 4. Merge Sorted Array / 合并两个有序数组 - 考点逆向归并、双指针 - 复杂度O(mn) / O(1) - 英文关键词merge, non-decreasing order - 卡点从前往后合并会覆盖原数组 今日翻译Two Sum 题干 新认识的 5 个术语indices, complement, such that, in-place, non-decreasing 一句话总结四道题都在练怎么在约束下省空间哈希和双指针是第六天最大的收获。这个模板的价值在于一周后回看能快速定位我当时卡住的是哪类问题。我发现大部分人的问题最终会收敛到几类边界条件、复杂度意识、题目理解偏差。没有复盘这些问题会被每天的新题掩盖有复盘才看得到自己的瓶颈在哪儿。5.3 第六天之后怎么让基础关真正过关从第六天开始我对基础关的定义不再是刷了多少道简单题而是能不能不看题解把学过的手法讲清楚。你可以用三个标准自测第一合上题解能不能独立写出两数之和的哈希解法第二看到原地修改 O(1) 额外空间能不能立刻条件反射到双指针第三拿到一道题能不能在 3 分钟内判断该上栈、哈希表还是双指针如果这三个都能做到就可以进入下一个阶段变式训练。所谓变式就是把第六天的四道题换皮。两数之和变成三数之和反转字符串变成反转链表有效的括号变成简化路径合并两个有序数组变成合并两个有序链表。你会发现这些新题看起来不同内部骨架还是第六天练的那套东西。基础题刷透之后中等题并没有想象中那么可怕——大部分中等题就是两个基础套路叠在一起罢了。我个人在第六天最深的体会是坚持到这一天并且开始做系统性记录比任何一次心血来潮的多刷几道题都重要。算法这条路没有捷径但复盘和翻译这两个动作能让每一步都踩得结实一点。