☰
零基础刷LeetCode:从数组双指针到贪心策略的入门路线
2026/10/10 22:38:58 网站建设 项目流程

写这篇指南之前,先说个很多入门选手都会踩的坑:一上来就刷热门一百题,结果链表、回溯、动态规划轮番上阵,每题都卡在“看题解秒懂、关掉题解手生”,反复几次心态直接崩了。我自己带过几个准备算法比赛的新人,最终发现真正有效的路径反而是老老实实从数组和字符串开始——因为绝大多数复杂算法,最后都会被拆解成对数组索引的移动、对字符串某个字符的存取、对区间内元素顺序的调整。数组和字符串不是“简单题”,而是整个算法地基的承重墙。

这篇文章围绕 LeetCode 零基础快速入门来写,以“27. 移除元素”“344. 反转字符串”“121. 买卖股票的最佳时机”三道题作为主干,把数组操作最核心的双指针技巧、索引边界控制,以及贪心策略入门的完整思考链一次讲透。这不是题解搬运,而是一个能直接套进你日常训练节奏的实操路径。无论你是在准备蓝桥杯、ACM校赛初筛,还是单纯想把 LeetCode 刷明白,这套思路都适用。

1. 为什么数组和字符串是算法的“基础载体”

很多人不理解,明明字符串和数组的题在 LeetCode 上被标成 Easy,为什么还要花专门时间反复练。我的回答是:正因为它们足够基础,反而最容易暴露你对内存布局、索引边界、指针移动这些底层概念的模糊理解。算法比赛的进阶题,本质上是把各种复杂操作包装在简单数据结构上,包装拆到最后,落点往往就是“这一格要不要覆盖”“这个指针什么时候向前走”“这个边界条件漏没漏”。

1.1 数组操作的核心:索引即一切

数组在内存中是连续存放的,访问第 i 个元素的时间复杂度是 O(1)。这个特性是所有数组类算法的大前提。很多人刷题时只记结论“双指针可以 O(n)”,却不理解为什么双指针能工作——因为数组支持随机访问,你可以同时维护两个下标,从不同方向逼近同一个区间,而不需要额外复制一份数据。

实际刷题中,最容易出问题的不是算法本身,而是索引边界。举一个最简单的例子:for (int i = 0; i < nums.length; i++)和for (int i = 0; i <= nums.length - 1; i++)在逻辑上等价,但在某些极端情况下(比如数组长度为 0),后者如果写成nums.length - 1且没有加括号,很容易出现负数下标的隐患。C++ 里nums.size()返回的是size_t(无符号整数),如果直接写nums.size() - 1而数组恰好为空,会得到一个巨大的正数,循环直接越界访问。这种问题在 LeetCode 上会直接报 Runtime Error,但在比赛现场,它可能让你在调试上浪费二十分钟。

所以我的建议是:每一道数组题,都要养成先问三个问题的习惯——数组可能为空吗?指针会不会走到数组外面去?当两个指针相遇时,当前这个位置到底算不算有效位置?这三个问题想清楚,代码基本就稳了。

1.2 字符串题本质是带限制的数组题

字符串的难点不在于“字符串本身”,而在于不同语言对字符串的实现差异。C++ 的std::string可以直接通过下标修改字符,Java 的String是不可变对象,Python 的字符串同样是不可变的,只能通过切片或list()转换后再赋值。同一个算法思路,在不同语言里写出来的代码差异极大。

因此,我强烈建议入门阶段选定一门主语言,把字符串的常用 API 全部过一遍:取长度、取子串、遍历、拼接、比较、翻转。不需要背 API 文档,但至少要清楚“当前语言里,字符串底层是连续内存还是对象数组”“修改字符串是否需要额外空间”。LeetCode 的 344. 反转字符串,就是一个典型的“字符串题当数组题做”的例子——题目要求原地修改,这就在提醒你:不要想着开新数组倒着填回去,而是要在原数组上直接操作。

2. 27. 移除元素:快慢指针从理解到熟练

题目描述很简单:给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,必须原地修改输入数组。

这道题之所以适合入门,是因为它把“双指针”中最基础的一种模型——快慢指针——展示得非常清楚。你不需要考虑复杂的排序规则,只需要一个指针负责往前探路,另一个指针负责记录“下一个有效位置应该写在哪里”。

2.1 快慢指针的思考过程

我第一次做这道题时,第一反应是:找到等于 val 的元素,删掉它,然后把后面的元素整体往前挪。这个思路在逻辑上没错,但时间复杂度是 O(n²)——每次删除都要搬移后续所有元素。对于比赛场景,这种写法一旦数据规模到 10⁵ 以上,直接超时。

快慢指针的思路完全不同:慢指针 slow 指向“当前已处理好的有效序列的末尾”,快指针 fast 负责遍历整个数组。每遇到一个不等于 val 的元素,就把它复制到 slow 指向的位置,然后 slow 前移一步。fast 走完整个数组后,slow 的值就是新数组的长度,且 nums[0] 到 nums[slow-1] 都是有效元素。

C++ 参考实现:

class Solution { public: int removeElement(vector<int>& nums, int val) { int slow = 0; for (int fast = 0; fast < nums.size(); fast++) { if (nums[fast] != val) { nums[slow] = nums[fast]; slow++; } } return slow; } };

Java 参考实现:

class Solution { public int removeElement(int[] nums, int val) { int slow = 0; for (int fast = 0; fast < nums.length; fast++) { if (nums[fast] != val) { nums[slow] = nums[fast]; slow++; } } return slow; } }

代码只有这么几行,但里面藏着三个必须想明白的细节:

第一,为什么可以直接覆盖 nums[slow]?因为 slow 一定小于等于 fast,当 slow 和 fast 指向同一个位置时,覆盖是不影响结果的;当 slow 小于 fast 时,nums[slow] 这个位置上的值要么已经被处理过、要么就是等于 val 的旧值,覆盖它不会丢失任何有效信息。

第二,为什么返回 slow 而不是 slow+1?因为 slow 指向的是“下一个空位”的下标,同时也是新数组的长度。举个例子:数组为 [3, 2, 2, 3],val=3,处理完后 slow=2,新数组是 [2, 2],长度为 2。如果你在循环结束时让 slow 加一,就错了。

第三,题目要求“移除后返回新长度”,但并没有要求你把后面多余的元素清零。LeetCode 只检查前 slow 个元素,所以后面残留的旧值不影响结果。

2.2 边界情况与常见错误

最容易翻车的边界情况有两个:

  • 数组本身就是空数组:此时 fast 循环根本不执行,slow 为 0,返回 0。这个要保证代码不要因为数组为空就崩。
  • 数组里所有元素都等于 val:比如 [3, 3, 3],val=3,循环里一个 if 都进不去,slow 始终为 0,返回 0。这是正确结果,但很多新手会觉得奇怪,以为至少要返回点什么。

另一个常见错误是把条件写反,写成if (nums[fast] == val)然后试图在循环里删除元素。在 C++ 的 vector 里用 erase 倒是能跑,但 erase 会让后面的迭代器失效,处理不当很容易越界;在 Java 的数组里你根本无法直接删除元素,只能覆盖。所以记住:这道题的正确姿势就是“不等于才复制”,不要试图去“删除”任何东西。

2.3 同类变体的迁移能力

这道题一旦吃透,整个“原地操作数组”系列都能顺势拿下来:

    1. 删除有序数组中的重复项:把“不等于 val”换成“不等于前一个元素”即可,几乎一样。
    1. 移动零:把“不等于 val”换成“不等于 0”,再把最后几个位置补零即可。
  • 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面:用对撞指针而不是快慢指针。

我在实际训练中,会把这类题打包在同一天刷完,因为它们本质上是同一套肌肉记忆。如果你能在一小时内连做四道同类型的题,并且不看题解写出来,说明这个知识点真的内化了,而不是背住了代码。

3. 344. 反转字符串:对撞指针与原地修改的边界感知

  1. 反转字符串的题干非常短:编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出,要求原地修改。

为什么强调“以字符数组形式给出”?因为如果是真正的字符串,Java 和 Python 里的 String 是不可变的,你没法原地改;而以char[]给出,就扫清了这层障碍,题目考察的重点就落在“如何用最少的操作完成交换”上。

3.1 对撞指针的思路与代码

反转字符串最朴素的做法是新建一个等长的字符数组,从后往前遍历填入,然后再复制回去。这个做法正确,但空间复杂度为 O(n)。题目既然强调了原地修改,就直接封死了这条路。

对撞指针的做法是:维护两个指针 left 和 right,初始分别指向数组首尾。交换 s[left] 和 s[right],然后 left 右移一位、right 左移一位,直到 left >= right 时停止。

C++ 参考实现:

class Solution { public: void reverseString(vector<char>& s) { int left = 0; int right = s.size() - 1; while (left < right) { swap(s[left], s[right]); left++; right--; } } };

Java 参考实现:

class Solution { public void reverseString(char[] s) { int left = 0; int right = s.length - 1; while (left < right) { char temp = s[left]; s[left] = s[right]; s[right] = temp; left++; right--; } } }

循环终止条件为什么是left < right而不是left <= right?因为当 left 和 right 相等时,中间的那个字符不需要交换——自己和自己交换没有意义。这个细微差别在代码层面少一次无谓操作,更重要的是,它帮你建立了“区间端点”的敏感度。对称的题在回文串判断、二分查找里反复出现,每一次都要确认:结束条件是相遇还是交错。

3.2 字符串题里最容易忽略的语言陷阱

如果你用 Java,需要格外注意s.length有没有括号。数组是属性length,字符串是方法length(),写混了编译都过不去。这种低级错误在比赛里不算算法问题,但真的会让你烦躁。

如果你用 Python,标准解法会更简短:

class Solution: def reverseString(self, s: List[str]) -> None: s.reverse()

这行代码直接调用列表的reverse()方法,原地反转。但我不建议入门阶段第一遍就用这个写法——你确实会做这道题了,但你对“双指针如何工作”的理解并没有提升。我的建议是:先用双手指针法手写一遍,跑通后再去看 Python 的reverse()实现,这样你才真正明白这个内置方法底层做了什么。比赛时用内置函数是合理的,但训练时一定要先裸写一遍。

另外,如果你后续做的是“反转字符串里的单词顺序”这类变形题,就需要先对整个字符串反转,再对每个单词单独反转。一旦遇到这种题,之前的对撞指针基础就会派上用场——因为它本质上就是“局部对撞”嵌套在“全局对撞”里。

3.3 反转之外的字符串常用操作清单

刷完反转字符串后,建议顺手把以下字符串基础操作过一遍,这些都是后续滑动窗口、动态规划、字符串匹配的前置技能:

  • 遍历字符:for (char c : s.toCharArray())或for (int i = 0; i < s.length(); i++)
  • 字符串转字符数组:Java 用s.toCharArray(),Python 用list(s)
  • 字符数组转回字符串:Java 用new String(charArray),Python 用''.join(charList)
  • 判断字符类型:Character.isLetter(c)、Character.isDigit(c),对应的是 125. 验证回文串 的前置处理
  • 字符串比较:Java 用equals(),C++ 直接==,Python 直接==,注意 Java 用==比较的是引用而非内容

这些操作每一项单独看都很普通,但它们组合起来的题,难度会直接翻倍。比如 125. 验证回文串,先要把大写转小写、过滤非字母数字字符,再双指针判断前后是否相同。没有上面这些基础操作,你连预处理都写不利索。

4. 121. 买卖股票的最佳时机:贪心策略从直觉到证明

  1. 买卖股票的最佳时机是 LeetCode 上非常经典的入门级别题目,但它在“零基础”和“有经验”两拨人眼里完全不是同一个难度。零基础看到题,第一反应是“找最低点和最高点”;有经验的人看到题,会立刻意识到这是一个“怎么用一次遍历完成最大差值计算”的问题,而且能顺手给出贪心策略的完整理由。

题目描述:给定一个数组 prices,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择某一天买入,并在未来某一天卖出,设计一个算法来计算你所能获取的最大利润。如果你不能获取任何利润,返回 0。

4.1 暴力解到贪心解的思维演进

绝大多数新手的第一个想法是双重循环:枚举买入日 i 和卖出日 j(j > i),计算 prices[j] - prices[i],取最大值。这个思路完全正确,但时间复杂度是 O(n²)。当 n 达到 10⁵ 时,就已经很难在比赛中过关了。

关键的问题来了:能不能在遍历一遍的过程中,同时记住“到当前位置为止见过的最低价格”?

可以。你从第 0 天走到第 i 天,如果有一个变量minPrice记录了前 i-1 天的最低价格,那么第 i 天卖出时,最大利润就是prices[i] - minPrice。你不需要知道具体是哪一天买入的,只要知道“之前最便宜的一天在哪一天的价格是多少”就够了。这是因为利润只与卖出价和买入价之差有关,在卖出日固定的情况下,买入价越低,利润越高,所以历史最低价就是最优买入价。

这个思路就是贪心策略的雏形——每一步都只看当前局部最优,不关心未来,因为这个局部最优决策不会阻碍后续的全局最优。换句话说,如果你今天卖出,历史最低点买入一定是最优解;而你保留这个历史最低点的信息,也不会影响后续任何一天作为卖出日的计算。

C++ 参考实现:

class Solution { public: int maxProfit(vector<int>& prices) { int minPrice = INT_MAX; int maxProfit = 0; for (int price : prices) { minPrice = min(minPrice, price); maxProfit = max(maxProfit, price - minPrice); } return maxProfit; } };

Java 参考实现:

class Solution { public int maxProfit(int[] prices) { int minPrice = Integer.MAX_VALUE; int maxProfit = 0; for (int price : prices) { minPrice = Math.min(minPrice, price); maxProfit = Math.max(maxProfit, price - minPrice); } return maxProfit; } }

4.2 为什么“找最低点再找最高点”不一定对

很多人会问:那我直接找到整个数组的最小值,再在它之后找最大值不就行了吗?这个想法很诱人,但它在一种情况下会失效——全局最低点出现在数组末端,而真正能产生最大利润的买入点并不是全局最低点。

举个例子:prices = [2, 7, 1, 4]。全局最低点是 1,但在 1 之后只有 4,利润是 3;实际最大利润是第 0 天买入 2、第 1 天卖出 7,利润是 5。如果你先找最低点再找后面的最高点,就错过了这个正确答案。

而贪心遍历的做法不会出错,因为它不是“先定位最低点再找最高点”,而是“在遍历过程中不断用最新价格作为卖出价,计算出历史最低买入价与当前卖出价的差值”。只要当前价格高于历史最低价,就尝试更新最大利润;如果当前价格比历史最低价还低,就更新历史最低价。每走一步,它都在做一次局部判断,而这次判断不会影响之前记录下来的最优值。

4.3 贪心策略的边界:为什么这道题可以用贪心

初学者最困惑的一点是:贪心到底什么时候能用,什么时候不能用?如果后面学到 122. 买卖股票的最佳时机 II(可以多次交易)就会知道,那道题也可以用贪心,每天只要价格上涨就累加差价;但一旦到 123. 买卖股票的最佳时机 III(限制交易次数为 2 次),贪心就失效了,必须用动态规划。

判断标准只有一个:局部最优决策是否能推导出全局最优,且不会排除其他更优路径。

对于 121 这道题,当前这一步的“更新最低价”和“尝试卖出”是完全独立的两个操作。更新最低价不会覆盖掉已经获得的利润记录,尝试卖出也不会让你失去未来更低买入的机会。因为在遍历到第 i 天时,你已经知道了前 i 天的所有价格,未来任何一天的更大利润,都可以用未来那天的价格减去当前已有的最低价来尝试更新,不需要回退。所以,一步步做出的局部最优决策最终就是全局最优。

4.4 从买卖股票延伸开的贪心应用场景

121 题刷完之后,可以立刻接上两道同源题:

    1. 买卖股票的最佳时机 II:多次交易,贪心解法是只要后一天价格高于前一天就累加差值,因为哪怕前一次卖早了,后一次买回来也仍然赚。
    1. 最大子数组和:同样是一遍遍历,维护“当前子数组和”和“历史最大子数组和”,遇到负和就重置。它的思考方式和 121 题几乎是一个模子刻出来的。

这三道题放在一起练,你会发现它们底层的“一次遍历 + 维持状态 + 逐步更新答案”模式完全一样。这就是为什么我说入门阶段不要贪题多,而要把同模式的题连着刷透——一旦你识别出“这个题只需要一遍遍历维护一个状态量”,你离想出解法就不远了。

5. 刷题节奏与比赛备赛的衔接建议

很多准备算法比赛的人会忽略一个事实:比赛不只是“会不会解”,还包括“快不快、稳不稳、能不能在高压下保持不犯低级错误”。LeetCode 的题解环境比较理想化,而比赛需要的是你在 30 分钟内完成从读题、建模、编码、测试到提交的完整闭环。

5.1 入门阶段的每日训练安排

如果时间和精力允许,我建议按下面的节奏执行:

  • 前 7 天:只刷数组双指针题。27、26、283、344、125,每天 2 到 3 道,目标是看到题目就能条件反射地想到“快慢指针”还是“对撞指针”。
  • 第 8 到 14 天:只刷一遍遍历维持状态的题。121、122、53、118 杨辉三角,重点是解释清楚“自己为什么这么设计状态变量”。
  • 第 15 天起:开始做混合训练,任意抽 5 道 Easy 题,限时 40 分钟做完。如果超时,不急着看题解,先自己用注释写出思路,再对照答案。

这个节奏的核心目的只有一个:在简单题里把代码习惯打磨好。比如变量命名是left/right还是i/j、边界条件是<=还是<、循环里能不能提前 return——这些细节在简单题阶段不修正,到难题阶段会放大成灾难。

5.2 比赛中的时间和空间复杂度敏感度

LeetCode 上很多题目的数据范围会给到 10³ 甚至更小,O(n²) 的暴力解也能过。但比赛不是这样——蓝桥杯省赛、ACM 校赛里,很多题默认就是 10⁵ 到 10⁶ 的数据规模,O(n²) 直接超时。所以从入门第一天起,我建议每写完一题,都顺手写下时间复杂度分析,不用写长,一行就行。

用本文三道题举例:

    1. 移除元素:O(n) 时间,O(1) 空间。快指针遍历一次,慢指针同步移动,没有额外数组。
    1. 反转字符串:O(n) 时间,O(1) 空间。交换操作耗时 O(1),左右指针总共移动 n/2 次。
    1. 买卖股票的最佳时机:O(n) 时间,O(1) 空间。一遍遍历,两个变量。

三道题全部是 O(n)+O(1),这其实也是入门数组题的标准形态。如果你以后碰到一道数组题,读完题发现可以只用常数个变量配合一次遍历就完成,那大概率就是一个贪心或双指针的模型。

5.3 调试技巧:print 流比断点更快

在 LeetCode 上提交前,我会习惯性在草稿纸上推演一个极小的用例。例如反转字符串,就写['a', 'b', 'c', 'd'],手动模拟 left=0、right=3 和 left=1、right=2,确认中间的字符不需要处理。这个习惯在比赛里特别有用,因为比赛环境往往不提供断点调试,你只能靠打印中间变量来判断问题在哪。

如果某段逻辑怎么都想不通,我常用的方法是临时加一个System.out.println("left=" + left + ", right=" + right);或者cout << "slow=" << slow << " fast=" << fast << endl;,把每一轮循环的状态打出来。打印的时候建议同时输出指针位置和当前数组的完整内容,否则只看指针位置很难发现问题。

而且,刷题阶段的代码一定要在本地 IDE 里配好测试用例再贴到 LeetCode。本地能跑通不代表线上能跑通,因为 LeetCode 的判题机在内存不足时行为可能和你本地不一致,但本地能跑通至少能筛掉一大半的语法错误和逻辑错误。

5.4 对零基础选手的几个具体建议

最后说几个我在带人过程中反复强调的点:

第一,不要背代码。背代码最典型的特征是:换一个数字、换一个变量名,你就不会写了。正确做法是背思路,比如“快慢指针:快指针负责找有效元素,慢指针负责存储位置;对撞指针:一左一右往中间逼近;贪心:遍历时维护历史最优状态”。

第二,遇到卡壳超过 30 分钟的题,直接看题解,但看完必须合上题解自己重写一遍。重写不出来就再看再重写,直到能不看题解写出来为止。这里的关键是“合上题解”,而不是“照着题解敲一遍”,差别非常大。

第三,准备一个错题本,不需要多精美,记录三件事:题目编号、卡住的原因(是没思路还是边界写错)、下一次要注意什么。比赛前翻一遍错题本,比刷十道新题管用得多。

第四,不要完全避开题意复杂的题。比如 8. 字符串转换整数 (atoi) 这种题,虽然思路不难,但全是边界处理:正负号、溢出、前导空格、非法字符。它考察的就是你在工程细节上的耐心。刷这种题能帮你建立“把题目要求一条条列出来,再一条条翻译成代码逻辑”的习惯,这个习惯在备赛过程中价值极高。

6. 从这三道题延伸出的算法思维主线

刷完这三道题,你已经在这个阶段拥有了三把基础武器:快慢指针处理覆盖型数组问题、对撞指针处理对称型区间问题、一次遍历加状态维护处理最值型问题。接下来要做的,是沿着这三条主线继续深化。

6.1 双指针家族:链表中也有双指针

数组里的快慢指针,放到链表里同样成立。LeetCode 876. 链表的中间结点、141. 环形链表,都是快慢指针的经典应用。区别在于数组的指针是下标,链表的指针是节点引用;数组可以随机访问,链表只能逐个 next。但“快指针走两步、慢指针走一步”的核心思想完全一致。

如果你准备比赛,链表和数组的双指针题混合刷,可以建立更强的抽象能力——同一个算法思想,在不同数据结构上的呈现方式不同,你能识别出底层的共同模式,就说明你是真的理解了。

6.2 贪心策略:一次遍历维持状态的广泛应用

买卖股票这道题的贪心解法,背后是一种更普适的思维模式:在遍历数据流时,维持一个“到当前位置为止的最优状态”,每来一个新数据就尝试用这个状态更新答案。

这个模式直接延伸到的题目包括:

    1. 最大子数组和:维护当前子数组和 curSum,如果 curSum 小于 0 就重置为 0,一直更新全局最大值。
    1. 买卖股票的最佳时机:维护历史最低价 minPrice,每天尝试卖出更新最大利润。
    1. 判断子序列:双指针,一个指针指向源字符串,另一个指向目标子序列,按顺序匹配。

这些题的共同特征都是:你要找到那个“随着遍历推进可以被维护的状态量”,而不是反复嵌套循环去枚举所有可能。

6.3 比赛常见的数据规模与策略参考

不同比赛的数据范围差别很大,我根据自己的比赛经历列一张表,方便你刷题时对照:

数据规模可接受的复杂度典型策略
n ≤ 20O(2ⁿ) 甚至 O(n!)暴力枚举、DFS 全排列
n ≤ 10³O(n²)双重循环、朴素 DP
n ≤ 10⁵O(n log n) 或 O(n)二分、排序、双指针、贪心
n ≤ 10⁶O(n)哈希表、双指针、线性 DP
n ≥ 10⁷O(n) 以下,通常要数学推导找规律、数论降维、矩阵快速幂

LeetCode 的 Easy 题大多属于 n ≤ 10⁵ 这一档,所以 O(n) 的解法基本就是最优解。但比赛有它的残酷性:即使你想到了 O(n log n) 的排序解法,如果常数因子太大,也可能在极端数据下超时。这就要靠刷题时对“常数级优化”保持敏感——比如能用数组下标映射就不要用哈希表,能用基本类型就不要用封装类型。

6.4 一个建议的进阶路线图

如果你完成了本文三个专项的训练,下一步我建议按照“查找 → 排序 → 哈希 → 递归 → 动态规划”的顺序继续推进。数组和字符串永远是这些进阶知识的载体,你在 27 题里练熟的双指针,后面在二分查找里还会频繁出现;你在 121 题里练熟的贪心,后面会演变成动态规划中的状态转移思想。

一些可以直接接在本文后面的题目:

  • 二分查找类:35. 搜索插入位置、704. 二分查找
  • 哈希表类:1. 两数之和、242. 有效的字母异位词
  • 滑动窗口类:209. 长度最小的子数组、76. 最小覆盖子串
  • 前缀和类:560. 和为 K 的子数组、303. 区域和检索 - 数组不可变
  • 字符串处理类:7. 整数反转、8. 字符串转换整数 (atoi)、151. 反转字符串中的单词

这些题的共同点,依然是建立在“数组索引操作”和“字符串预处理”这两块地基上。地基打得越稳,后面建楼越快。

最后分享我个人的一点体会:刷题初期不要被“刷了多少题”绑架。我见过太多人一个月刷一百多题,但问到 27 题为什么返回 slow 而不是 slow+1,还是会愣住。与其追求数量,不如把每道题背后的思维模式拆干净。比如今天这三道题,你如果能在不看任何资料的情况下,清晰讲出双指针的三种形态(同向快慢、异向对撞、双端探测),能把贪心策略在 121 题上的“为什么局部最优等于全局最优”讲明白,那我敢说,你比那些“刷了两百题但只看题解”的人,离真正的算法能力更近。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询