写这篇东西的起因是上周组里一个师弟问我师兄我用Java刷LeetCode是不是天生吃亏啊C选手一行指针玩出花来Python选手一行库函数搞定Java代码又长又笨我怎么刷都感觉不如别人快。这个问题我在刚入行时也纠结过很久。当时持续刷了三个月题做了两百多道面试时碰到变形题照样卡住。后来才想明白问题不是出在Java身上而是出在刷题方式上。LeetCode的定位从来不是比谁代码写得短而是考察你在限定时间内拆解问题、设计算法、落地实现的能力。Java在这条赛道上反而有独特优势标准库丰富、IDE调试能力强、类型系统能逼你把边界条件想清楚这些都是实打实的加分项。这篇文章主要聊三件事怎么纠正刷题姿势、怎么搭建一套顺手到不行的Java刷题环境、以及从AC到面试之间的最后一公里怎么走。无论你是准备校招、社招跳槽还是单纯想提升算法内功都可以参考。1. 先纠正刷题姿势题海战术为什么越刷越心虚聊方法论之前我想先泼一盆冷水。很多人刷题半年不见长进不是智商问题也不是Java的问题而是刷题姿势从一开始就歪了。1.1 三类被浪费的“努力型刷题”第一类是按题号顺序硬刷。从第1题开始遇到Hard就死磕一整天磕不出来就看题解看完觉得自己懂了下一题继续。这种刷法最大的问题是你只是在“做过题”没有在“建立模式”。人的大脑不擅长记忆离散的解题步骤它擅长记忆的是“问题特征”和“解法模式”之间的对应关系。按题号刷恰恰把相同模式的题目打散了。第二类是只刷不总结。AC完一题绿色对勾一亮马上打卡收工。沾沾自喜“我今天又做了一道题”但没停下来想这题考的是哪个数据结构为什么是这个复杂度有没有更好的解法和昨天那题有什么相似之处不总结的刷题等于去健身房拍照发朋友圈但从不看动作要领练一年肌肉线条也不会有变化。第三类是背题解刷“高频题”。面经里说某某题出现频率高就把它背下来。背了二十道面完发现人家变形了当场傻眼。这类人不是刷题是刷“彩票”。“背答案”和“会解题”之间的差距面试官只需要追问一句“为什么这里用HashMap不用数组”就能分辨出来。1.2 专题化复盘节奏我用的刷题框架我自己摸索出来的框架很简单就三个关键词。第一是“专题化”。把一个数据结构或者算法方向集中刷透比如两周只搞滑动窗口下一周只搞二叉树。同一专题的题会反复调用同一个核心思路见得多了你就会慢慢形成“看到某种特征就想到某种解法”的条件反射。这个道理和健身房练肌肉一样专项训练比每天换动作瞎练高效得多。第二是“复盘”。我给自己定了三步复盘清单AC之后先看官方题解或者讨论区最高赞解法对比思路差异然后分析自己的代码时间和空间复杂度看有没有优化空间最后在题解笔记里写一句话总结核心是“这道题让我记住了一个什么模式”。三步加起来十分钟左右但收益远远大于多刷三道题。第三是“节奏”。工作日一天一题保持手感周末抽半天做专题突破和参加周赛。不要心血来潮一天刷十题然后停一周刷题的持续性比单次爆发更重要。这一套方法我在校招和社招两个阶段都验证过稳定出效果。接下来要做的就是先把刷题的环境和工具准备到位。2. 一套Java版刷题底稿环境、快捷键与测试用例模板很多Java程序员刷题的日常是打开LeetCode网页把代码写进网页里的编辑器跑通AC复制粘贴到本地就完事。这样刷不是不可以但效率真的低。网页编辑器没有智能提示、没有断点调试、没有快速重构遇到复杂一点的题光靠肉眼看代码找bug就能耗掉你四十分钟。2.1 把LeetCode拉进IDEA插件与模板配置我目前的刷题环境是IntelliJ IDEA LeetCode插件。插件可以直接拉取题目、选择语言、填写代码并提交还能在本地直接运行调试用起来体验很顺。装好插件后在Settings里配置好登录信息然后把默认语言设为Java把自定义代码模板配置好让每次新建题目都带一个Main方法和测试脚手架。配置好之后我还会自定义几个Live Templates。比如输入main后自动生成public static void main(String[] args)模板输入sout自动生成System.out.println。这些事看起来小而碎但刷题状态下能少打几个字就少打几个字能把注意力集中在题本身。下面是我个人最常用的一套底稿模板适合绝大多数题目类型import java.util.*; public class Main { public static void main(String[] args) { Solution solution new Solution(); // 在这里构造你的测试用例 int[] nums {2, 7, 11, 15}; int target 9; System.out.println(Arrays.toString(solution.twoSum(nums, target))); } } class Solution { public int[] twoSum(int[] nums, int target) { MapInteger, Integer map new HashMap(); for (int i 0; i nums.length; i) { int complement target - nums[i]; if (map.containsKey(complement)) { return new int[]{map.get(complement), i}; } map.put(nums[i], i); } return new int[0]; } }注意几个细节类名统一用Main方法里new Solution()来调用核心逻辑。这样主类里永远在干“构造测试数据 调用解法 打印结果”这一件事和LeetCode提交入口剥离开调试时心智负担更小。2.2 本地测试三件套序列化、断言与压力数据LeetCode页面上做题测试用例是系统帮你构造好的。本地调试时最麻烦的就是链表、二叉树这类特殊结构怎么构造。我整理了三个工具方法建议直接存进自己的模板里。第一个是数组转单链表public static ListNode arrayToList(int[] arr) { ListNode dummy new ListNode(0); ListNode cur dummy; for (int v : arr) { cur.next new ListNode(v); cur cur.next; } return dummy.next; }第二个是层序数组转二叉树public static TreeNode arrayToTree(Integer[] arr) { if (arr null || arr.length 0 || arr[0] null) return null; TreeNode root new TreeNode(arr[0]); QueueTreeNode queue new ArrayDeque(); queue.offer(root); int i 1; while (!queue.isEmpty() i arr.length) { TreeNode node queue.poll(); if (arr[i] ! null) { node.left new TreeNode(arr[i]); queue.offer(node.left); } i; if (i arr.length arr[i] ! null) { node.right new TreeNode(arr[i]); queue.offer(node.right); } i; } return root; }第三个是打印链表或数组的辅助方法直接写一个通用toString就好刷题用不上JUnitSystem.out.println就够了。工具类备齐后一定要养成“边界数据压力数据”双测试的习惯。比如刷排序题光测普通数组是不够的得测空数组、单元素数组、全相等数组、已经有序的数组、超大数组。LeetCode判题系统会很贴心地给你边界用例但本地测试时如果你主动构造过这些数据你写代码时就会下意识地考虑边界条件这个习惯在面试中非常加分。3. 用Java语言特性拆解高频算法专题环境搭好之后接下来是刷题的重头戏怎么借力Java语言本身的特性把一类题目刷出效率来。这里我讲四个高频专题说白了这也是LeetCode Hot 100里出现频率最高的几个套路。3.1 HashMap搞定八成“找关系”类题目两数之和这道题是很多人的LeetCode初恋也是HashMap应用的典型代表。暴力解是O(n²)而用HashMap把“已经看过的数”存起来每次只需要O(1)时间查一下目标差值在不在里面整体时间复杂度直接降到O(n)。这个思想本质上是“空间换时间”的教科书级案例。为什么Java刷这类题特别舒服因为有HashMap这个现成的数据结构并且默认实现就是处理了哈希冲突的链式结构。做变位词分组、最长连续序列、和为K的子数组这类题思路一旦落到“用哈希表记录什么”Java基本一路顺手写下去。还有一个冷门但很重要的类LinkedHashMap。我刷LRU缓存那道题时一开始自己手写双向链表写了整整一屏代码。后来发现Java的LinkedHashMap本来就支持按访问顺序排序配合removeEldestEntry方法重写十行代码搞定LRU。用语言自带的特性去解决问题不是偷懒是合理利用工具工作和面试都是这样。class LRUCache extends LinkedHashMapInteger, Integer { private final int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); this.capacity capacity; } public int get(int key) { return super.getOrDefault(key, -1); } public void put(int key, int value) { super.put(key, value); } Override protected boolean removeEldestEntry(Map.EntryInteger, Integer eldest) { return size() capacity; } }3.2 排序、双指针与滑动窗口Java标准库的正确用法很多Java程序员刷排序题时内心是虚的总觉得“Arrays.sort()是作弊”。我的观点很直接比赛和面试中直接用标准库排序完全没问题但前提是你必须能说清楚它的底层机制。Java的Arrays.sort对基本类型用的是双轴快排对对象类型用的是TimSort两者的区别网上一搜一大把面试官问起来的概率很高。你不仅要会用还要能解释为什么对象数组排序用归并排序更稳定。双指针则是另一个高频套路。有序数组的两数之和、三数之和、接雨水、盛最多水的容器核心都是“左右指针根据当前结果决定往哪个方向移动”。这类题用Java写起来也不复杂注意边界条件就行。滑动窗口类题目在Hot 100里占比也不低。无重复字符的最长子串、最小覆盖子串、字符串排列等核心套路就一句话维护一个窗口右边扩、左边缩用个计数器或者哈希表记录窗口状态。这类题的代码模板相对固定刷到三五道以后基本就能形成肌肉记忆。3.3 栈与递归从二叉树到表达式求值的共同骨架二叉树题是Java面试的重灾区因为递归写法太简洁很多人AC完根本没搞懂递归栈是怎么走的。这里我建议做一个练习每道树的题先用递归写一遍再手动模拟一遍递归过程。比如翻转二叉树递归的返回值是什么递归到null时返回什么这些问题想清楚了树的题就通了一大半。栈这个数据结构在表达式类题目里不可或缺。括号匹配、逆波兰表达式、基本计算器、每日温度等都是栈的典型应用。这里要单独提醒一点Java里优先用ArrayDeque而不是Stack类。Stack继承自Vector内部有锁实现上是历史遗留的同步容器性能上不如ArrayDeque。而且ArrayDeque的push/pop语义完全等价于栈操作代码写起来没有区别但面试时提一句为什么不用Stack反而能显出你对Java容器源码的熟悉程度。4. 一道Hard题从抽搐到AC基本计算器的完整拆解方法论和工具都说完了我挑一道带有“劝退”属性的经典题和大家完整走一遍解题过程LeetCode 224题基本计算器。之所以选它一是因为它在“leetcode热门100题”和面试题单里都跑不掉二是它同时覆盖了栈、状态机、边界处理三个难点啃下这道题你对表达式类问题的理解会上一个台阶。4.1 为什么基本计算器是经典Hard题目本身很好理解给定一个字符串包含数字、加号、减号、括号和空格计算其结果。比如输入(1(452)-3)(68)输出23。看起来就是个小学算术题但难点在于括号改变了运算顺序而且可能出现多层嵌套括号。这类题真正的工程价值在于它是编译器语法解析的雏形。你写出的每一个表达式计算器本质上都是在做词法分析和语法分析。搞清楚这道题之后去看解释器、计算器引擎之类的源码会有一种豁然开朗的感觉。4.2 符号反转法拆掉括号的数学本质这道题最常见的解法分两类双栈法和符号反转法。我推荐符号反转法因为它代码更短、逻辑更清晰而且能让你直观理解“括号为什么能改变结果”。核心思想是把一个表达式拆成若干个带符号的数相加。比如1 - 2 3其实就是1 (-2) 3。当遇到左括号时括号里的符号可能会因为括号前面的负号而反转比如1 - (2 - 3)把括号拆掉后就变成1 - 2 3。所以我们需要维护一个符号栈记录每一层括号当前的“符号状态”。数字的累加逻辑也简单碰到数字字符就把它累进当前数字直到碰到运算符或者括号才把当前数字结算并加到结果上。这个“延迟结算”的思想是整个算法最容易写漏的地方。4.3 完整实现与两个高频翻车点看完整实现class Solution { public int calculate(String s) { int result 0; int num 0; int sign 1; DequeInteger stack new ArrayDeque(); for (char c : s.toCharArray()) { if (Character.isDigit(c)) { num num * 10 (c - 0); } else if (c || c -) { result sign * num; num 0; sign (c ) ? 1 : -1; } else if (c () { stack.push(result); stack.push(sign); result 0; sign 1; } else if (c )) { result sign * num; num 0; result * stack.pop(); // 弹出括号前的符号 result stack.pop(); // 弹出括号前的结果 } } result sign * num; return result; } }这段代码有两个高频翻车点我在本地调试时都踩过这里专门说一下。第一个坑是字符串结尾的结算。如果字符串最后是数字循环结束时num里还存着最后的数字但循环没有机会触发运算符分支来结算它。所以循环结束后必须补一行result sign * num。很多人第一次写都会漏AC前常见报错是1这种单数字用例输出0。第二个坑是括号出栈的顺序。注意我在遇到左括号时先push了当前结果再push当前符号。出栈时正好相反先pop出来的是符号后pop出来的是之前的结果。如果这个顺序搞反了输出结果会面目全非。我建议你调试时在1-(2-3)这个用例上手动走一遍栈的push和pop过程理解之后就不会再错了。5. 从AC到面试现场复杂度分析与代码工程的临门一脚很多人刷题刷到AC就停手了但我想说AC只是及格线。真正拉开差距的是AC之后你还能不能回答出“这题为什么是这个复杂度”“这个代码在极端情况下会不会出问题”“能不能把这套思路用在工程代码里”。这一节我用三个维度来聊。5.1 面试官问你“复杂度多少”均摊、最坏与常见误判没有面试官会满足于你只说一个“O(n)”。他可能会追问HashMap的查询是严格的O(1)吗如果哈希函数设计得不好导致大量key冲突退化成链表那查询就退化成O(n)了。Java的HashMap对冲突做了红黑树化优化但面试官想听的是你懂不懂“最坏情况”和“均摊情况”的区别。这个在刷题时就要养成习惯。每次AC后别急着提交先在草稿纸上写一遍时间复杂度和空间复杂度推导过程。递归类的题画一下递归树循环嵌套类的题算一下内层循环总共执行多少次。练多了面试现场被问到的时候就不会慌。5.2 AC之后代码还差哪些工程化改造LeetCode上AC的代码和工厂里的生产代码差距是很大的。最大的问题在于“约定大于规范”方法名是题目给的变量名随手写参数不做防御性校验。在公司里写代码代码是要给别人读、要经过review的命名要表达意图输入校验要做边界条件要兜底。我在练习时会做一个动作AC之后把Solution里的核心逻辑抽出来改名成有业务含义的方法。比如两数之和的解法我会改成findTwoSumIndexes参数改成有明确语义的变量名再补上对空数组的防御判断。这个习惯坚持半年后我写的刷题代码和业务代码之间的“割裂感”会明显减少面试时手撕代码也会更从容。5.3 高频必刷题单从Hot 100到周赛的选题策略关于选题给出一份我目前比较推荐的Java版刷题路径。不要盲目去刷全站几千道题按专题和频率来选更合理。专题代表题目核心考点Java注意点哈希表两数之和、最长连续序列、字母异位词分组空间换时间、键的设计HashMap与LinkedHashMap选型链表反转链表、环形链表、LRU缓存指针操作、虚拟头节点优先用List接口理解别背模板滑动窗口无重复字符的最长子串、最小覆盖子串窗口扩张与收缩字符计数用int[26]更快二叉树二叉树的中序遍历、最大深度、二叉树的最近公共祖先递归终止条件、返回值设计推荐用ArrayDeque模拟栈二分查找爱吃香蕉的狒狒、寻找旋转排序数组中的最小值单调性判断、边界收缩注意mid计算防溢出动态规划爬楼梯、最长递增子序列、打家劫舍状态定义、转移方程熟练后练习空间压缩栈与表达式基本计算器、有效的括号、每日温度栈状态维护用ArrayDeque替代Stack排序与双指针三数之和、接雨水、最大数排序后有序性利用手写快排练一次理解原理这份题单不必逐题按顺序刷正确用法是选一个专题把表里对应题目做完然后自己再去找三五道同类题巩固保持“专题内加餐”的习惯。6. 长期手感管理周赛节奏、题单维护与复习机制刷题这件事最怕的不是题目难而是刷完就忘。上个月会做的题今天重新拿起来居然连切入点都想不起来。这种挫败感我太熟悉了。后来发现遗忘不是记忆力差而是缺少一套“手感保鲜”机制。6.1 周赛的正确打开方式先保底再冲刺大部分人对周赛的态度是“怕”怕自己排名难看怕四道题只做出一道。我的建议是把周赛当作每周一次的“模拟面试”来对待而不是排名竞争。周赛的题目分布是有特点的前两道通常考察基础数据结构操作第三道开始进入算法设计第四道往往是压轴题。和面试流程高度相似。我的周赛策略是前十五分钟先把前两道题干净利落地AC第三道题读三遍确保理解题意如果十分钟内没有明确思路果断跳过把时间留给能拿分的题。第四道题能写多少写多少哪怕只有一个朴素解也比空白提交有价值。周赛结束后一定要补题。哪怕只补一道题也要把题目做透看官方题解、看最优解、分析自己当时的卡点是什么。上周末那场周赛我就明显感觉手感比前几次稳前三题全部一次AC最后一个Hard拿到了朴素解的分。量和质之间就是这种节奏感在拉差距。6.2 刷题纪录表与间隔复习防止“刷了就忘”我强烈推荐每个人建一个自己的刷题记录表不一定要在线平台本地Excel、Notion、甚至一个云文档都可以。记录的内容不是“题号AC状态”而是下面几条日期与题目名称所属专题一句话思路总结比如“看到有序数组找两个数先想到双指针”复杂度分析的结论是否需要复习超过30分钟想出思路的标记为需复习然后按这个节奏复习当天刷的题三小时后看一遍错点三天后重做一遍七天后再用这题的思路去解一道同专题新题。这个间隔复习的周期是我用过效果最好的比每天盲目重刷有效得多。日常节奏上我给自己定的规矩是工作日每天一题周末半天周赛加复盘另一个半天做专题拓展。这样下来不会累但每周都能感受到自己在进步。我个人在实际操作中的体会很深很多刷题坚持不下来的人不是败给了题目难度而是败给了“没有正反馈”。如果你也觉得刷题无聊可以试着把记录表做得好看一点或者拉上一两个同事互相监督甚至给组内新人布置每周一题讲题给别人听是最强的记忆方式。最后说一个小技巧如果你今天状态很差就挑一道已经做过的Medium题不看题解重写一遍这比硬啃新题更能恢复手感这是我的私家压箱底经验。