如果让我给准备刷 LeetCode 的新人一个最朴实的建议,那就是先打透数组和字符串这两个专题。市面上的刷题攻略很多,但真正适合零基础开局的,永远是这一块:它不依赖复杂的数据结构,不要求先懂图论、动态规划,却能把双指针、贪心、暴力枚举这些算法基础动作练扎实。这篇指南我选了 27. 移除元素、344. 反转字符串、121. 买卖股票的最佳时机 三道题,它们分别代表了数组经典操作、字符串基础处理、贪心策略入门,也是我当年从零刷题时真正"开窍"的三道题。不管你之后是想应付笔试、备战蓝桥杯,还是参加 LeetCode 周赛,先把这几道题吃透,后面的路会顺很多。
1. 为什么数组和字符串是算法入门的必修课
1.1 零基础的第一步到底该怎么走
很多人一开始刷题,喜欢按"难度"来,上来就干 Hard,结果一道题卡三天,挫败感直接拉满。我自己踩过这个坑,后来才慢慢悟出一个规律:算法比赛也好、大厂笔试也好,真正拉开差距的往往不是那种需要灵光一现的压轴题,而是基础题做得够不够稳、够不够快。数组和字符串就是这种"稳"字的来源。
零基础阶段做的事情其实很单纯:先掌握一门语言的基本语法,再学会数组初始化、遍历、取长度、访问下标这些操作,然后就开始按专题刷。数组是几乎所有容器类题目的底层载体,字符串在底层的形态就是字符数组。你先把数组相关的遍历、覆盖、交换练熟了,再看链表、二叉树、哈希表,都会有"这不就是换了个壳子吗"的熟悉感。
1.2 数组加字符串为什么是算法基座
说一个数据:LeetCode 热门 100 题里,至少六成以上题目的核心操作都发生在数组或字符串上。排序、二分、双指针、滑动窗口、前缀和、哈希去重、单调栈……这些算法你追根溯源,最后都会落到数组元素的访问和更新上。
字符串就更直接了,它本质是字符数组。你能对数组做的事,比如按下标访问、原地修改、交换元素、拼接复制,几乎都能映射到字符串上。反过来,字符串多了"逆序输出"、"子串匹配"、"拼接拆分"这些特有操作,其实也就是在数组操作外面加了一层约束。把这两块吃透,等于给后面所有高级算法铺了一条稳稳的下脚路。
1.3 备赛视角下的专题地位
如果你目标不只是刷题,而是想打比赛,那数组和字符串更是绕不开的必经之路。力扣周赛里,A 题和 B 题大概率是数组或字符串的简单题、中档题;蓝桥杯省赛也经常在填空和编程题里塞进数组操作。比赛比的不仅是会不会,还有谁写得快、谁不容易在边界条件上翻车。
所以我才说这个专题是"入门必刷":它投入产出比最高、试错成本最低,又能帮你把竞赛需要的基本功——暴力枚举、原地操作、状态记录、双指针扫描——全部练到位。
2. 27. 移除元素:双指针思想的第一课
2.1 题目解析与暴力思路的局限
- 移除元素 的要求很简单:给你一个数组
nums和一个值val,原地移除所有数值等于val的元素,返回移除后数组的新长度。注意两个关键字:原地、新长度。你不能开一个临时数组把不等于val的元素装进去再拷回来,必须在这个数组本身身上做文章。
我第一反应是暴力枚举:遍历数组,每次遇到等于val的元素,就把后面的所有元素整体往前移动一位,然后长度减一。这个思路没错,问题是复杂度太高了。最坏情况下数组里全是val,每次移动都是 O(n),整体就是 O(n²)。在 LeetCode 上数据量一大,直接就给你超时教育。刷题和打比赛为什么强调复杂度?因为暴力解法虽然很多时候能过小数据,但在正式比赛的数据规模下,O(n²) 基本等于没戏。
2.2 快慢指针的写法与原理
正确的做法是双指针,具体来说是快慢指针。慢指针slow维护"下一个要写入的位置",快指针fast负责从头到尾扫描。遇到不等于val的元素,就把它写到slow指向的位置,然后slow前进一步;遇到等于val的元素,直接跳过。最后slow的值就是新数组长度。
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]; } } return slow; } };核心思路是"覆盖",不是"删除"。数组的删除操作本质上就是覆盖:把要保留的元素往前挪,把不要的元素彻底遮挡在有效长度之外。这里为什么不会丢失未来需要的元素?因为slow指向的位置要么已经是无效位置,要么是已经被处理过的旧值,覆盖它不影响后面还没扫描到的数据。这个"写指针负责落位、读指针负责扫描"的模式,在后面很多题目里都会反复出现,比如删除有序数组中的重复项。
2.3 还能更快的首尾双指针与边界细节
27 题还有一个特别提示:元素的顺序可以改变。这时候可以用首尾双指针,思路有点不一样:left从左往右找等于val的元素,right指向"新数组末尾的下一个位置"。
class Solution { public: int removeElement(vector<int>& nums, int val) { int left = 0, right = nums.size(); while (left < right) { if (nums[left] == val) { nums[left] = nums[right - 1]; right--; } else { left++; } } return right; } };注意这里有个隐蔽的坑:当nums[left] == val时,我们用nums[right - 1]覆盖nums[left],但不能立刻left++,因为从尾部搬过来的元素可能也等于val,必须下一轮再判断一次。只有nums[left] != val时才left++。这个细节我见过不少人在笔试里栽跟头。
边界情况也要在提交前过一遍:数组为空时,right = 0,返回 0 没问题;数组里没有val,left一路走到right,返回原长度;数组全是val,right一路缩小到 0。还有一个 C++ 特有问题:nums.size()返回的是无符号数,直接拿来做int right = nums.size() - 1这种操作,在空数组时会变成一个巨大的正数,所以要么转成int,要么在函数开头判空。这种低级错误最划不来。
3. 344. 反转字符串:把双指针练成条件反射
3.1 从数字数组到字符数组,本质没有变
- 反转字符串 给的输入是字符数组
s,要求原地反转,不能申请额外空间。这题和数组题的关系一目了然:字符串在题目里以字符数组的形态出现,你需要像操作数组一样操作它。很多人在学字符串时,习惯用语言自带库函数,比如 Python 的切片、C++ 的reverse,三两行就写完。但放在算法题和面试场景里,考官真正想考察的是你能不能手写出这个反转过程。
这题还有一个变体场景值得留意:如果你是在笔试现场手写输入输出,经常会遇到"字符串逆序输出"这种基础小题;而 344 题练的就是这个基本功。理解了数组的"交换"和"对称下标"两个概念,字符串反转就是顺水推舟。
3.2 标准解法与语言细节
思路非常简单:两个指针,一左一右,交换所指元素,然后向中间移动,直到相遇。因为是对称操作,最多只需要遍历半个数组。
class Solution: def reverseString(self, s: List[str]) -> None: left, right = 0, len(s) - 1 while left < right: s[left], s[right] = s[right], s[left] left += 1 right -= 1注意 Python 这里有个典型错误:如果写成s = s[::-1],其实是在创建新列表,原传入的s并没有被修改,力扣判定会失败。要原地修改,可以写成s[:] = s[::-1],但为了练习双指针还是建议手写。C++ 版本也很直接,用swap(s[left], s[right])就行,注意别让right从无符号的size()直接减一,先转成int更安全。
3.3 从一道题延伸出一个题型
344 虽然简单,但它是很多字符串题的底座。后面的 541. 反转字符串II 就是在这个基础上加了一点点边界控制:每 2k 个字符反转前 k 个。再往后,反转字符串里的单词、反转字符串中的元音字母,核心动作都是这个对称交换。
我刷题时的经验是:这种简单题至少要手写三遍,第一遍看题解,第二遍合上书默写,第三遍试着改动条件。比如把条件改成"每 k 个反转一次"或者"只反转字母",你能马上写出来,说明真的掌握了,而不是背了一个模板。这也是为后续做中等难度字符串题打底,省得后面一边写代码一边现想怎么写交换。
4. 121. 买卖股票的最佳时机:贪心策略的经典入口
4.1 为什么暴力枚举在比赛中会超时
- 买卖股票的最佳时机 是贪心策略的一道经典入门题。题目给你一个数组
prices,prices[i]表示第i天的股票价格,你只能选择某一天买入,再选择未来某一天卖出,求最大利润。如果赚不到钱,返回 0。
最直觉的解法是暴力枚举:两层循环,把所有i < j的组合都算一遍,记录最大值。这个方法理论上能保证答案正确,但价格数组的长度上限是 10^5 级别,O(n²) 会稳稳超时。比赛里数据范围一给,时间限制一卡,暴力就是来陪跑的。你要做的是从暴力解法里找规律,提炼出更高效的状态记录方式。
4.2 一遍遍历贪心策略拆解
这题核心规律在于:第 i 天卖出能获得的最大利润,只取决于前 i-1 天里的最低价格。你不需要枚举所有买入点,只需要一路走、一路记录历史最低价,同时计算"今天的历史最低价买入、今天卖出"能赚多少钱,并更新历史最大利润。
class Solution: def maxProfit(self, prices: List[int]) -> int: min_price = float('inf') max_profit = 0 for price in prices: if price < min_price: min_price = price elif price - min_price > max_profit: max_profit = price - min_price return max_profit这个策略为什么叫贪心?因为每一步都只做局部最优决策:看到更低的价格,就把它更新为新的买入候选点;看到更高的利润,就把它更新为新的答案。整个过程没有回头改写的操作,但局部最优一步步累加,最终得到了全局最优。这也是贪心算法最干净的入门案例:结构简单、证明直观、代码好写。
实际写代码时,min_price初始值可以用float('inf')(Python)或INT_MAX(C++),这样第一个元素一定会被当作初始历史最低价。如果数组是空的,循环不执行,max_profit保持 0,也能正确返回。C++ 版本里,如果没有INT_MAX头文件,也可以直接初始化为prices[0],但要先判断数组非空,不然下标越界。
4.3 从贪心到动态规划,以及系列题延伸
关于 121 题,还有一件事得说清楚:力扣官方把它同时标了"贪心"和"动态规划"。从 DP 角度理解,可以定义dp[i]为前i天能获得的最大利润,转移方程是dp[i] = max(dp[i-1], prices[i] - minPrice)。因为这个状态只依赖前一个状态和一个滚动变量,所以可以把数组压掉,写成上面的遍历版本。理解这一层,后面做 122. 买卖股票的最佳时机II、309. 最佳买卖股票时机含冷冻期 时,思路会顺很多。
这套"维护一个候选状态、一个答案状态"的写法,不仅在股票题里频繁出现,在很多贪心题里都是一模一样的骨架:一个变量存储当前情况下的最优选择,另一个变量存储全局答案。把 121 这题吃透,等于给自己种下了一个通用模板。
5. 零基础刷题避坑记录与备赛建议
5.1 高频报错和边界测试清单
刷题最难受的不是不会做,而是"感觉逻辑对了,一提交就红"。我整理了几条新手最容易踩的坑,全是自己测试过或者帮别人 debug 时见过的:
| 常见问题 | 典型场景 | 处理办法 |
|---|---|---|
| 返回值理解错 | removeElement 返回新长度,却忘了原地修改数组 | 提交前先读两遍题目:返回值是什么、数组是否要原地改 |
| 空数组越界 | C++ 中nums.size() - 1变成无符号大数 | 先判空,或者强制转成int再减一 |
| 固定测试用例过了,边界没过 | 数组全相等、数组只有一个元素、目标值不存在 | 提交前自己构造 4-5 个边界用例跑一遍 |
| 贪心变量初始化错误 | min_price初始化成 0,导致后续更新失效 | 用无穷大或第一个元素初始化 |
| Python 原地修改失效 | s = s[::-1]没有真正改传入数组 | 用s[:] = s[::-1]或手写双指针 |
5.2 力扣与人机交互竞赛的环境差异
很多人在力扣上做题顺风顺水,一打比赛就发懵,原因是环境变了。力扣是"函数式填空":你只需要实现一个函数,输入输出由系统帮你处理好。但像蓝桥杯、牛客竞赛模式,往往要自己写main函数、自己处理标准输入输出,格式还可能是多组数据。备赛之前,务必去牛客或蓝桥杯官网做几套往年真题,熟悉一下从控制台读入、格式化输出、多组数据循环的流程。这步不练,上了赛场可能连数据怎么读都卡住。
另外,比赛里如果要提交 C++ 代码,vector、string这些 STL 头文件要自己背清楚。平时在力扣上,头文件是自动帮你加好的,但竞赛不是。建议在本地搭一套干净的编译环境(VS Code 或 CLion 都可以),自己写一遍完整的main和头文件引用,别等到比赛现场再查。
5.3 时间投入、刷题节奏与复盘方法
零基础刷题最怕"三分钟热度后陷入刷了忘、忘了刷"的循环。我的建议是前四周不要贪多,每天认真做 1-2 道专题题,每周参加一次力扣周赛(哪怕只 AC 一题,也是锻炼心态)。
复盘比刷新题更重要。每道题做出来后,至少看一眼官方题解和评论区的高赞解法,对比自己差在哪。重点记录两样东西:一是这道题用到的"算法模式",比如双指针、贪心、二分;二是自己卡壳的具体点,是边界没考虑,还是思路没转过来。隔一周再重做一次,能独立 AC 才算真正掌握。这个方法听起来慢,但实际效果比我当年闷头刷三百道题强得多。
最后再分享一个小技巧:这三道题可以组成一套"热身组合拳",每次开始刷题前先用 10 分钟手写一遍移除元素的双指针、反转字符串的双指针、买卖股票的一次遍历。连续写两周,你会发现双指针和贪心这两种思维开始变成肌肉记忆。后续无论你往图论、动态规划还是回溯方向走,这几个基础动作都会陪伴你很久。