零基础刷LeetCode:先吃透数组与字符串,双指针贪心全掌握
如果让我给准备刷 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(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; } };核心思路是覆盖不是删除。数组的删除操作本质上就是覆盖把要保留的元素往前挪把不要的元素彻底遮挡在有效长度之外。这里为什么不会丢失未来需要的元素因为slow指向的位置要么已经是无效位置要么是已经被处理过的旧值覆盖它不影响后面还没扫描到的数据。这个写指针负责落位、读指针负责扫描的模式在后面很多题目里都会反复出现比如删除有序数组中的重复项。2.3 还能更快的首尾双指针与边界细节27 题还有一个特别提示元素的顺序可以改变。这时候可以用首尾双指针思路有点不一样left从左往右找等于val的元素right指向新数组末尾的下一个位置。class Solution { public: int removeElement(vectorint 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 没问题数组里没有valleft一路走到right返回原长度数组全是valright一路缩小到 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 为什么暴力枚举在比赛中会超时买卖股票的最佳时机 是贪心策略的一道经典入门题。题目给你一个数组pricesprices[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_MAXC这样第一个元素一定会被当作初始历史最低价。如果数组是空的循环不执行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 分钟手写一遍移除元素的双指针、反转字符串的双指针、买卖股票的一次遍历。连续写两周你会发现双指针和贪心这两种思维开始变成肌肉记忆。后续无论你往图论、动态规划还是回溯方向走这几个基础动作都会陪伴你很久。