1. 为什么说简单题和中等题才是刷题的主力区
我最早开始刷力扣的时候,和大多数人一样,上来就挑困难题硬刚。结果很真实:一道题看两个小时,题解看了三遍还是云里雾里,最后怀疑自己是不是不适合写代码。后来我换了个思路,把重心放到简单和中等难度上,刷题效率反而上来了,面试时的表现也比之前稳定得多。
很多人对“简单”和“中等”这两个难度标签有误解,觉得简单题太水、中等题不够看,要刷就刷困难题才能体现水平。实际上,力扣的题目难度划分是面向真实面试场景的:简单题考察的是基本功是否扎实,中等题考察的是能否在有限时间内把常见套路用出来,困难题更多是竞赛向或选拔向的压轴内容。国内大厂的技术面试,绝大多数算法题都落在简单到中等这个区间,真正考困难题的公司少之又少。就算考了,也是作为加分项而不是一票否决项。
把简单和中等刷透,是在用最小的成本换取最大的面试收益。这个道理我后来带几个朋友准备面试时反复讲,他们从怀疑到认同,最后拿到offer之后都跟我说,早知道先把简单和中等吃透就好了。
1.1 简单题决定你的状态与节奏
简单题的作用不是让你练智商,而是帮你建立手感和信心。我实测过一个数据:如果某段时间每天稳定刷4到5道简单题,一周之后再看中等题,明显感觉思路打开得快,边界条件也不容易漏。
简单题真正考验的东西有三样:一是对编程语言基础API的熟悉程度,比如字符串切割、数组排序、哈希表的增删查改;二是对基本数据结构的敏感度,看到“找唯一出现一次的数”能不能条件反射想到异或,看到“合并两个有序数组”能不能立刻写出双指针;三是对时间复杂度的直觉,虽然简单题一般不需要复杂优化,但至少要知道暴力解在什么数据范围下会超时。
我见过不少基础不错的人栽在简单题上,原因不是不会做,而是大意。比如“字符串转整数”这题看着简单,实际要处理空格、正负号、溢出、非法字符四类情况,能一次性写对的人并不多。简单题其实是很好的“体检工具”,它能暴露你平时写代码时习惯性忽略的细节。
节奏方面,我推荐“少量多次”而不是“一次猛刷”。每天固定时间刷几道简单题,比周末一次性刷20道效果好得多。算法思维需要连续刺激,断档两三天再捡起来,热乎劲就没了。
1.2 中等题才是区分能力的真正战场
力扣的中等题,难度跨度其实很大。有的中等题无非是简单题套了一层壳,读完题目就能想到做法;有的中等题则需要两到三个知识点叠加,比如前缀和配二分、栈配合单调性、动态规划加状态压缩。但不管怎么变,中等题的核心特征是:它一定有明确的“套路入口”,不像困难题那样经常需要灵光一现。
拿我自己的刷题统计来说,中等题里出现频率最高的几类是:数组与哈希配合、双指针与滑动窗口、链表操作、二叉树遍历、DFS与BFS、基础的动态规划。这些类型加起来占了中等题的一大半。换句话说,中等题表面看起来千变万化,实际上考点就那些。把每个套路的典型题目吃透,再遇到新题时,做的就是匹配和组合的工作。
另外,中等题是面试中“区分度”最高的区间。面试官让你做一道简单题,大概率是想看看你的代码风格和沟通方式;让你做中等题,则是考察你面对一个不熟悉的问题时,能不能快速定位考点、设计合理方案、控制边界条件。很多候选人简单题答得飞快,一到中等题就卡住,或者写出来的代码能过样例但扛不住边界测试,这都是在中等题这个环节暴露的问题。
所以我的建议很明确:如果你的目标是面试,把简单题当作热身和复习素材,把中等题当作主战场。刷透中等题,比做十道难题更划算。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心题型拆解:简单与中等最常考的套路
刷题不能靠蛮力,要有意识地建立“题型-解法”的映射关系。我自己刷到一定量之后,看到题目能自动在脑子里弹出几个候选方案:这题是双指针还是哈希?能不能用二分?是不是要维护一个单调栈?这个能力不是天生的,是反复归类训练出来的。
这一节我把力扣简单和中等题里出现频率最高的几类套路拆开讲,每类给一个代表性的题目和关键思路,帮你快速建立自己的解法弹药库。
2.1 数组与哈希:空间换时间的典型
数组题是力扣最简单也最基础的一类,但数组题往往不只是考数组本身,而是考“怎么在数组上做高效查找”。最经典的例子就是“两数之和”,这可能是全网刷题人数最多的一道题。
暴力解法是两层循环,时间复杂度O(n^2),数据量小的时候能过,数据量一大就直接超时。用哈希表可以把查找时间从O(n)降到O(1),整体时间复杂度降到O(n)。
cpp复制vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> hash;
for (int i = 0; i < nums.size(); i++) {
int need = target - nums[i];
if (hash.count(need)) {
return {hash[need], i};
}
hash[nums[i]] = i;
}
return {};
}
为什么哈希表在这里是“标准答案”?因为题目要求返回下标,而哈希表天然适合“值到下标的映射”。如果题目改成返回排序后的两个数,那用双指针加排序更合适。这就是我在这一节开头说的:解题不是套模板,而是先分析题目约束,再选匹配的数据结构。
中等题里,数组和哈希经常和“前缀和”搭配出现。比如“和为K的子数组”这道题,暴力枚举所有连续子数组是O(n^2),但用前缀和加哈希记录每个前缀和出现的次数,一次遍历就能算出答案。我第一次看这个解法时觉得妙极了,后来发现这其实是一类题的通法:只要问题涉及“连续区间求和等于某个值”,就可以想想前缀和。
2.2 双指针与滑动窗口
双指针是力扣出镜率最高的技巧之一,核心思想是用两个指针协同遍历,把O(n^2)的暴力循环降到O(n)。最经典的场景是“有序数组的两数之和”和“反转字符串中的元音字母”,这两道都是简单题,但双指针思想一脉相承。
中等题里,双指针最常见的变体是滑动窗口。滑动窗口特别适合处理“子串”“子数组”类问题,比如“无重复字符的最长子串”“长度最小的子数组”。这类题有一个共性特征:题目问的是连续区间的最优解,而且区间端点都是单调移动的。
以“无重复字符的最长子串”为例,核心思路是维护一个窗口,右指针不断向右扩展,遇到重复字符时左指针收缩到重复字符的下一个位置。配合哈希表记录每个字符最后一次出现的位置,代码很简洁:
cpp复制int lengthOfLongestSubstring(string s) {
unordered_map<char, int> last;
int left = 0, ans = 0;
for (int right = 0; right < s.size(); right++) {
if (last.count(s[right])) {
left = max(left, last[s[right]] + 1);
}
last[s[right]] = right;
ans = max(ans, right - left + 1);
}
return ans;
}
滑动窗口的难点不在于代码本身,而在于判断“什么时候移动左指针”。我的经验是:先想清楚窗口的“合法条件”是什么,再想“不合法时怎么收缩”,最后想“窗口更新后要不要更新答案”。这三步顺序理清了,大多数滑动窗口题都能写出来。
2.3 链表操作:画图是最好的调试工具
链表题在力扣里属于“看着简单、写着容易翻车”的类型。核心原因是链表是引用结构,指针操作稍不留神就会断链,或者产生环。我每次写链表题都会在草稿纸上画图,先画出原始链表,再画出目标状态,最后才写代码。
简单题里,“反转链表”是最基础的一道。迭代法用三个指针,pre指向当前节点的前一个,cur指向当前节点,next保存下一个节点,然后逐个把当前节点的next指向前一个节点。
cpp复制ListNode* reverseList(ListNode* head) {
ListNode* pre = nullptr;
ListNode* cur = head;
while (cur) {
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
你可能会问,为什么要单独存next?因为cur->next一旦被改成pre,原来的下一个节点就找不到了,所以必须在改之前先存下来。这个细节就是链表题最常见的坑。
中等题里,“删除链表的倒数第N个节点”特别值得练手。第一反应可能是先遍历一遍算出链表长度,再走第二遍删除目标节点。但这题的经典解法是双指针:快指针先走N步,然后快慢指针一起走,快指针到末尾时,慢指针恰好指向倒数第N个节点的前一个位置。这种做法只遍历一次,而且代码写出来会漂亮很多。
链表题的另一个常见套路是“虚拟头节点”,也就是在链表头部加一个不参与业务的哑节点。它的作用是简化边界处理,特别是删除头节点这种操作,有了虚拟头节点就不需要考虑特殊情况了。“合并两个有序链表”这题,用虚拟头节点配合逐个比较,代码会非常整洁。
2.4 二叉树的遍历与递归
二叉树是递归思想最好的练兵场。简单题里,“二叉树的最大深度”只需要递归求左右子树深度再取最大值加一,代码不超过五行的核心逻辑。但真正考察的是你懂不懂递归的“信任模型”:不要试图在脑子里展开整个递归过程,而是相信函数定义本身。
中等题里,二叉树的题目通常从“遍历”扩展为“根据遍历结果构建树”或者“在遍历过程中同步计算”。比如“验证二叉搜索树”,如果只检查当前节点和左右孩子的大小关系,是错的,因为二叉搜索树要求左子树所有节点都小于根节点,右子树所有节点都大于根节点。正确做法是利用中序遍历,检查结果是否严格递增。
code复制bool isValidBST(TreeNode* root) {
TreeNode* pre = nullptr;
return inorder(root, pre);
}
我在“验证二叉搜索树”这道题上栽过一次,因为写成了只比较局部节点大小,结果有个测试用例过不去,这个用例的左子树右节点比根节点大。后来我才明白,树的问题不能只看局部,中序遍历的全局视角才是正解。这个认知对后面刷更多树相关的题目帮助很大。
3. 实操过程:一道题从看到AC的全流程
很多刷题的人有个误区,觉得做题就是看一眼题目,然后埋头写代码,写对了就过。但真实面试中,写代码只是整个环节的三分之一。更重要的是你如何理解题目、如何和面试官沟通思路、如何证明你的方案是靠谱的。
我在刷题过程中逐渐固化了一套个人流程,每道题都按照这个流程走,效率和正确率都明显提升。这一节我把这套流程拆开讲,并且用一道经典题目完整走一遍。
3.1 拿到题目后的五步检查法
我在拿到任何一道题之后,哪怕是简单题,也会强制自己走完五步:
第一步,把题目用自己的话复述一遍。如果连自己都讲不清题目在要求什么,那说明还没理解到位,应该重新读题。复述时要特别注意输入范围,因为输入范围直接决定了解法能不能用暴力。
第二步,确定输入规模和数据特征。力扣题目一般会给出数组长度、数值范围等约束。自研题目时也会根据数据量判断预期的复杂度。比如n在10^5级别,O(n^2)大概率超时,O(n)或者O(n log n)才是方向。
第三步,想暴力解,再想优化。不要一上来就追求最优解,先把最直观的做法想清楚,再一步步优化。这不仅是为了确保有保底方案,更重要的是通过暴力解来理解题目的本质结构。
第四步,确定数据结构和算法方向。这一步靠的是平时积累的“题型-解法”映射。比如有序数组加查找,基本可以确定是二分或者双指针;字符串匹配类问题,想想动态规划还是滑动窗口。
第五步,自测边界条件。代码写完不是终点,要自己构造极端数据来验证。空数组、单元素数组、全是重复元素、已经有序、完全逆序,这些情况都要过一遍脑子。
这五步看起来繁琐,但形成习惯之后每道题只需要多花一两分钟,却能避免大量“提交后才发现错了”的尴尬局面。
3.2 一个完整的例子:从“有效的括号”看栈的应用
“有效的括号”是力扣上一道经典的简单题,但它背后牵出的“栈”这个数据结构,在中等题里出现频率极高。很多中等难度的表达式求值、括号生成、字符串解码问题,本质都在考栈的应用。
题目本身不复杂:给定一个只包含()[]{}的字符串,判断括号是否匹配。比如“()[]{}”是有效的,“([)]”是无效的。我的处理方式是:遍历字符串,遇到左括号就入栈,遇到右括号就检查栈顶是否匹配,匹配就出栈,不匹配直接返回false;遍历结束后,如果栈不为空返回false。
cpp复制bool isValid(string s) {
unordered_map<char, char> match = {
{')', '('}, {']', '['}, {'}', '{'}
};
stack<char> st;
for (char c : s) {
if (match.count(c)) {
if (st.empty() || st.top() != match[c]) return false;
st.pop();
} else {
st.push(c);
}
}
return st.empty();
}
这题为什么会用到栈?因为括号匹配具有“后进先出”的特性:最晚出现的左括号一定要先被匹配。这种天然的顺序一致性,正是栈的核心语义。理解了这一点,遇到类似的嵌套结构题目时,你会下意识想到栈,而不是靠死记硬背。
这道简单题还可以继续往中等题方向延展:比如“字符串解码”就是栈与数字、字符串组合的产物。所以别只把简单题当简单题做,每做完一道简单题,想一想“如果这题再加一点嵌套条件,会变成什么样子”,这个思考过程本身就是在为中等题打基础。
3.3 复杂度分析:面试官真正想看的东西
力扣提交页面会显示你的代码运行时间和内存消耗,很多新手喜欢追求击败百分之九十以上的人,但我建议不用太执着于这个数据。不同语言的基准不同、判题机的负载也会波动,这个百分比经常差个百分之十,参考价值有限。
真正需要在意的是算法本身的复杂度。面试官问“你的时间复杂度是多少”,绝对不是随口一问,他想知道你有没有评估过自己的方案在数据量放大之后还能不能扛住。所以我们平时写题就要养成习惯,每写完一道题,主动算一算时间复杂度和空间复杂度。
时间复杂度怎么算?我用的方法是数循环层数。单层循环一般是O(n),双层循环是O(n^2),排序是O(n log n),二分是O(log n),递归要画递归树来推算。哈希表的读写均摊是O(1),但最坏情况是O(n),力扣一般不会构造这种极端哈希冲突数据,不过心里要有数。
空间复杂度则看额外开辟了多大的辅助结构。比如“有效的括号”这题,最坏情况下字符串全为左括号,栈的深度和字符串长度一样,空间复杂度就是O(n)。如果使用原地算法,不申请额外空间,那就是O(1)。
我曾经辅导过一个朋友,他两数之和用哈希解法写出来,问他空间复杂度,他愣了两秒才说不知道。这不是不会算,是平时做题时没有把复杂度分析当成必要步骤。后来我强制他每道题写完都要在纸上写出时间和空间复杂度,坚持了一周之后,他做艺考题都顺手很多。建议你也试试这个方法。
4. 常见问题与排查技巧实录
刷题刷多了之后,我发现大家跪的点其实高度集中。这一节整理我在刷题和陪跑过程中经常遇到的四类问题,每个问题都附上排查思路和解决方案,方便你直接对照参考。
4.1 超时和内存溢出的处理思路
超时是最常见的报错,英文是Time Limit Exceeded。第一次遇到时不用慌,这恰恰说明你的思路有个大问题:要么是算法复杂度太高,要么是存在不必要的重复计算。
排查思路分三步:第一步,重新看输入规模。如果n是10^5级别,你用了O(n^2)的解法,百分之百超时,不用怀疑。第二步,检查循环内部有没有做低效操作。比如循环内频繁进行字符串拼接,C++里是用+拼接字符串会反复分配内存,改用push_back或reserve就能优化很多。第三步,看看能不能用缓存,把重复计算存下来。递归里最容易出现重复子问题,这时候引入一个数组做记忆化搜索,往往就能轻松过关。
内存溢出在力扣上一般表现为Memory Limit Exceeded。这个相对少见一点,常见的诱因是递归层数太深导致栈溢出,或者申请了一个超大的二维数组。比如动态规划问题里,如果dp数组只需要上一行的数据,为什么非要存储全部历史行?滚动数组技巧就是干这个的。
我的一个实测心得是:面试现场出现超时,比出现答案错误更难处理。因为答案错误还能对着测试用例调试,超时往往意味着整个方案要推倒重来。所以越是简单的题目,越要在动手写之前想清楚复杂度,不能觉得“暴力码能出结果”就上了。
4.2 边界条件的经典坑位
边界条件是力扣题的天然陷阱。一道题你核心逻辑写对了,但边界没处理,提交后就是Wrong Answer,而且大概率是挂在某个你根本没想到的用例上。
我总结的高频边界坑位有四个:空输入、单元素输入、极端大小值、重复元素。空输入好理解,数组长度为0,字符串为空,节点为nullptr。单元素输入则是为了验证循环和递归的退出条件是否正确。极端大小值主要用来测溢出,比如整数相加超过int范围,力扣里有些题目的答案本身就可能超过int限制,要提前想好要不要用long。重复元素则考验去重逻辑,如果题目要求唯一组合,这里一定要处理。
举一个最常见的例子,“搜索旋转排序数组”这类二分变种题,很多人的代码在数组只有一个元素时会死循环或者直接出错。因为二分循环的退出条件没写对,比如while (left <= right)写成while (left < right),配合内部更新逻辑,就会漏掉最后一个元素。
我自己的习惯是:每道题写完,先手动构造三个测试用例:空输入、最小规模输入、含重复元素的输入,把这些跑通了再往上提交。这个习惯帮我减少了很多无用提交。
4.3 思路卡壳时的自救策略
刷题最痛的时刻不是报错,而是完全没思路。一道中等题看了十分钟,脑子里一片空白,不知道从哪里下手。这种时候我有几个自救策略,按顺序尝试,成功率比较高。
第一,缩规模。想象输入数据只有1个或者2个,用手模拟一遍整个过程,看看能不能发现规律。很多题目的解题规律其实就是在小规模下自己跑出来的。第二,看样例。力扣的样例一般给得很好,仔细对比输入输出,反推中间过程,有时候能直接看出应该维护什么样的数据结构。第三,暴力枚举。先在纸上写暴力做法,再逐层优化。暴力解不是最终答案,但它是理解问题的跳板。第四,回顾题型映射。问自己,这题是数组问题还是字符串问题?需不需要排序?是不是要维护一个最值?这些“自问自答”能帮你把题目挂到已知的套路上。
如果以上所有方法都试过了,还是没思路,那就果断看题解。不要有心理负担,看题解本身也是学习的一部分。真正的关键是看完题解之后,要自己独立写一遍,并且隔一两天再重写一次,直到能流畅默写出来为止。
我见过太多人刷题只是“看懂了”就划过去,结果再遇到类似题还是不会。原因就是没完成“从看懂到会写”的过程。刷题真正的收获发生在你合上题解、自己从头敲出代码的那十分钟里。
4.4 刷题节奏与复现计划
最后聊一个没那么“技术”但同样重要的问题:刷题节奏。没有计划地乱刷,容易陷入“刷一道忘一道”的循环。我自己尝试过很多方案,最终沉淀下来的节奏是这样的。
第一轮,按题型刷。选定当前要突破的数据结构,比如数组哈希,就集中刷这一类的简单和中等题,每道题都记录题型标签和关键思路。这一轮的核心目的是建立“题型-解法”的映射库。第二轮,按题目刷。随机刷合集,练习快速识别题型。这轮开始会有意识地计时,模拟面试场景,简单题控制在15分钟内,中等题控制在30分钟内。第三轮,复习错题和题解。我刷题初期每道题都整理了简短笔记,写清楚思路卡点、错误原因和最优解法,复习时只看这些笔记。
除了长期节奏,每天也要固定投入。我的个人习惯是每天1到2道题,遇到状态好的周末加量。不追求一天刷十道,更看重持续不断。算法能力不是一蹴而就的东西,它更像练肌肉,每天做一点力量训练,比半个月突击一圈有效得多。
关于复现计划,我强烈推荐“间隔重复法”。新做过的经典题,在第1天、第3天、第7天各重做一次。第一次重做是确保你真的会写,第二次重做是检验是否已经内化为自己的解法,第三次重做就是条件反射级别了。这套方法我在自己身上实测有效,带的朋友照着做,反馈也不错。
我个人在实际操作中的体会是:刷力扣这件事,最难的从来不是某一道具体的难题,而是能否保持稳定输出的节奏。简单题让你不手生,中等题让你有收获感,两者搭配起来,才能形成一个可持续的正循环。当你坚持两三个月后发现,自己拿到一道中等题已经能快速想到解法方向时,那种感觉比做对十道难题都踏实。
