十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java刷力扣Hot100必备:本地环境搭建与模板设计实战

Java刷力扣Hot100必备:本地环境搭建与模板设计实战 刷力扣的人一多大家最后都会卡在同一个问题上到底在网页编辑器里直接写还是自己在本地搭一套环境我的答案很明确尤其是以Java为主语言的朋友非常值得把力扣Hot100的题目在本地搞一套顺手模板一次性解决输入输出、调试、数据结构定义这些体力活后面所有题目都能复用。这篇文章就从头讲清楚我怎么搭、为什么这么搭以及整套模板踩过的坑。先说这套东西能解决什么问题。力扣Hot100是面试前最高性价比的题单基本覆盖了算法面试的绝大多数考点。但如果只在网页上写代码一长就很容易迷失在输入输出的解析里调试也不方便尤其是链表、二叉树这类题目数据结构的构造在网页上看不到细节全靠脑补。本地模板就是把环境先用代码铺好让你只需要专注在核心算法逻辑上而且跑测试用例的速度比网页快很多。适合谁所有打算认真刷Hot100、又希望把代码沉淀下来的Java学习者已经上班但想保持手感的朋友同样适用。1. 先想清楚本地模板到底解决什么问题1.1 网页刷题的隐性成本很多人觉得力扣直接在网页上写最方便题解区复制粘贴也快。但刷到三五十题之后你会发现几个问题非常烦人。第一每次写链表或者二叉树题目都要重新在脑子里过一遍节点的定义虽然力扣的代码框里会自动带出ListNode和TreeNode类但你在本地想跑通就得自己维护这两段基础模板。第二网页上的调试能力有限虽然现在有print日志但处理复杂输入时比如树结构的层序遍历表示法你很难直观看到中间状态。第三点最容易被忽视就是代码的沉淀问题。你在网页上写的代码默认是分散的、不可跑的做完一道题之后想回头复盘往往只能看题解和自己的代码差异。但如果你在本地有一个格式统一的工程每个题目一个类带上测试用例过半年再翻出来仍然能一键运行、一眼看懂当时怎么想的。这个价值只有亲身体会过才知道。针对Hot100这种题单来说还有一个非常现实的好处Hot100的大多数题目可以归到有限的几类模板里比如二叉树的遍历、链表的反转、回溯的框架。本地模板的价值不在于省那几分钟的脚手架时间而在于让你每次写题的时候都处于一个稳定的代码上下文里减少和刷题无关的负担。1.2 版本选型为什么是Java、为什么是Hot100选择Java作为刷题语言对求职的人有天然的兼容性。很多公司的面试现场要求写Java因为团队主栈就是Java你如果平时用Python刷题面试切换回来总会有一点点生疏。Java的表达相对啰嗦但反过来也有优势类型明确有助于把思路理清尤其是复杂数据结构的处理写起来比动态语言更有安全感。Hot100本身是力扣官方运营维护的高频题集它不是一个固定的100题而是动态更新的列表。但无论如何变化高频这个属性是稳定的。高频意味着它在面试中出现的概率大同样也意味着值得为它投入工程化的准备。别小看这一点把花在重复劳动上的时间省下来多过几遍真正的算法思路性价比极高。2. 环境准备从JDK安装到本地工程落地2.1 JDK怎么选、怎么装、环境变量到底怎么配Java刷题不需要用太新的版本JDK 8或者11就够了。力扣的编译器现在已经支持到比较新的Java版本但面试场景很多公司还是JDK 8、11所以本地用JDK 8或者11能避免遇到一些新版本才有的语法糖然后又不能在面试现场用造成的尴尬。我个人推荐JDK 11长期支持版本性能稳定新的var关键字如果你想用它也是支持的但我建议刷题时尽量不用。JDK安装本身不复杂但环境变量是新手必踩的坑。Windows下安装完JDK后需要配置三个东西JAVA_HOME、PATH和CLASSPATH。JAVA_HOME指向JDK的安装目录比如C:\Program Files\Java\jdk-11.0.20。PATH里要新增%JAVA_HOME%\bin这个步骤让java和javac命令只在任何目录都能用。CLASSPATH在新版本的JDK里已经不需要配了但如果你用的是很老的习惯模板会配上.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar这个其实在JDK 8以后不是必须的建议干脆不配避免类加载时的各种诡异问题。配置完成后在命令行里挨个输入java -version和javac -version验证。如果java能输出版本但javac提示找不到大概率是PATH里没有配%JAVA_HOME%\bin或者配置后没有重新打开命令行窗口。这个坑90%的Java新手都踩过实际排查起来非常简单。macOS下用Homebrew安装更省事brew install openjdk11然后根据提示配置JAVA_HOME即可。Linux用户用apt install openjdk-11-jdk或者yum道理都一样关键是配好环境变量后记得用source或者重开终端。2.2 IDE选择与最小可用的工程骨架Java刷题我首选IntelliJ IDEA社区版免费对单模块的Java工程支持非常好。你也可以用Eclipse或者VS Code加Java插件但IDEA的调试体验在刷题场景下是最舒服的尤其是表达式求值、条件断点这些功能排查复杂题目的中间状态时极为好用。创建工程时不需要上Maven或者Gradle这种重量级构建工具。新建一个普通的Java项目在src目录下按题目创建类即可。我的习惯是分两个代码目录一个是template放所有的基础模板另一个是solution放每道Hot100题目的解法和测试用例。如果习惯用包名隔离也可以设置成com.leetcode.template和com.leetcode.hot100按个人喜好来。核心诉求是整个工程能在IDEA里一键Build每个题目类都能单独Run。我实际维护的工程结构大概是这样的java-leetcode/ ├── src/ │ ├── template/ │ │ ├── ListNode.java │ │ ├── TreeNode.java │ │ ├── Main.java │ │ ├── ArrayParser.java │ │ └── TreeBuilder.java │ └── solution/ │ ├── S0001_TwoSum.java │ ├── S0015_ThreeSum.java │ ├── S0042_TrapRainWater.java │ └── ... └── README.md每个题目标号保持和力扣一致这样后期复盘找题非常快。类名里带上题号和题目缩写比如S0042_TrapRainWater一眼就能看出来是哪道题。这个命名的价值在于当你刷到300题的时候回头想找某个题目的代码不需要去翻力扣的提交记录直接按文件名找就行。3. 模板工程的核心设计让输入输出不再浪费时间3.1 链表、二叉树的定义与工具方法Hot100里链表题和二叉树题加起来差不多有30道左右所以ListNode和TreeNode这两个类必须提前写好而且要尽量贴近力扣官方的定义避免在本地运行和提交之间产生逻辑差。ListNode按力扣的定义来public class ListNode { public int val; public ListNode next; public ListNode() {} public ListNode(int val) { this.val val; } public ListNode(int val, ListNode next) { this.val val; this.next next; } }TreeNodepublic class TreeNode { public int val; public TreeNode left; public TreeNode right; public TreeNode() {} public TreeNode(int val) { this.val val; } public TreeNode(int val, TreeNode left, TreeNode right) { this.val val; this.left left; this.right right; } }但只定义这两个类还不够。本地刷题最大的难点在于构造测试数据比如链表要自己串节点树要从层序遍历数组还原。如果每次手写这几段刷题效率会大打折扣。所以我建议给这两个类各写一个build静态方法。链表的构造非常简单从一个数组生成链表public static ListNode build(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; }树的构造稍微麻烦一点力扣的树输入是层序遍历的数组空节点用null表示。比如[3,9,20,null,null,15,7]就代表一棵二叉树。还原的逻辑要用队列逐层建节点public static TreeNode build(Integer[] arr) { if (arr null || arr.length 0) return null; TreeNode root new TreeNode(arr[0]); QueueTreeNode queue new LinkedList(); queue.offer(root); int idx 1; while (!queue.isEmpty() idx arr.length) { TreeNode node queue.poll(); if (arr[idx] ! null) { node.left new TreeNode(arr[idx]); queue.offer(node.left); } idx; if (idx arr.length arr[idx] ! null) { node.right new TreeNode(arr[idx]); queue.offer(node.right); } idx; } return root; }这段代码看着简单但有几个细节需要注意。数组里null的位置不能跳过否则树的结构就错了。idx每处理一个节点就必须按顺序走两步因为层序遍历数组里每个节点对应左右两个孩子的位置即使孩子是null位置仍然被占用。3.2 数组、字符串等输入解析模板数组是最常见的输入类型。力扣网页版的函数参数已经是解析好的数组了但本地你要从字符串或者控制台读入。[1,2,3,4]这样的格式直接用正则或者手写解析都不够通用我建议写一个通用的ArrayParser工具类。public class ArrayParser { public static int[] parseIntArray(String s) { s s.trim(); if (s.startsWith([)) s s.substring(1, s.length() - 1); if (s.isEmpty()) return new int[0]; String[] parts s.split(,); int[] res new int[parts.length]; for (int i 0; i parts.length; i) { res[i] Integer.parseInt(parts[i].trim()); } return res; } public static Integer[] parseIntegerArray(String s) { s s.trim(); if (s.startsWith([)) s s.substring(1, s.length() - 1); if (s.isEmpty()) return new Integer[0]; String[] parts s.split(,); Integer[] res new Integer[parts.length]; for (int i 0; i parts.length; i) { String part parts[i].trim(); res[i] part.equals(null) ? null : Integer.parseInt(part); } return res; } public static String[] parseStringArray(String s) { s s.trim(); if (s.startsWith([)) s s.substring(1, s.length() - 1); if (s.isEmpty()) return new String[0]; return s.split(,); } }为什么要单独写一个工具类因为Hot100里很多题目的入参不止一种比如字母异位词分组输入的是字符串数组三数之和输入的是整数数组还有的题目输入是二维数组。如果每个题目都临时写一段解析逻辑代码会非常散。抽成工具类以后每个main方法里三行以内就能完成数据准备。二维数组的解析稍微复杂一点下面这个是我常用的写法public static int[][] parseIntMatrix(String s) { s s.trim(); if (s.startsWith([)) s s.substring(1, s.length() - 1); String[] rows s.split(\\],\\[); int[][] res new int[rows.length][]; for (int i 0; i rows.length; i) { String row rows[i].replaceAll([\\[\\]], ); if (row.trim().isEmpty()) { res[i] new int[0]; } else { String[] nums row.split(,); res[i] new int[nums.length]; for (int j 0; j nums.length; j) { res[i][j] Integer.parseInt(nums[j].trim()); } } } return res; }这种解析方式虽然用了一些字符串操作但对刷题来说完全够用也比引入额外的JSON库轻量得多。3.3 Main模板手写Java刷题时的标准入口我在本地维护了一个Main.java模板这里的main方法不是某个题目的入口而是用来临时测试工具类或者快速跑一段数据结构的验证代码。它最大的作用是在最开始就把输入输出框架搭好单独调试某一个数据解析逻辑时直接用这个入口。另外还有一个建议Hot100里的每个题目类我都写一个独立的main方法里面放上标准的输入解析、构造输入数据、调用Solution里的核心方法、打印输出结果。为了不和力扣要求的类名冲突我把核心算法逻辑放在内部类Solution里外部类名用自己命名的Sxxxx_xxx。提交的时候只复制内部类方法体逻辑不会丢。public class S0001_TwoSum { public static void main(String[] args) { int[] nums ArrayParser.parseIntArray([2,7,11,15]); int target 9; int[] result new S0001_TwoSum().twoSum(nums, target); System.out.println(Arrays.toString(result)); } 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]; } }这样的好处是每个类既可以独立运行验证又保留了力扣提交时需要的核心方法签名。配合IDEA的Run按钮鼠标一点就能看到输出调试体验远好过网页端。4. Hot100题型拆解与刷题顺序的实操建议4.1 Hot100到底在考什么Hot100的高频考点听起来很多但归纳起来就几大类数组、链表、哈希表、字符串、双指针与滑动窗口、二分查找、栈与队列、二叉树、回溯、动态规划、贪心、图论、堆、并查集、字典树等。其中二叉树和动态规划是面试中的绝对重点其次是链表和双指针。明白了考什么你搭建本地模板的时候就知道应该优先准备哪些工具类。以我个人的经验Hot100里的二叉树题目至少有20道上下动态规划也有15到20道。这两个板块加一起基本就是半张卷子。所以本地模板里二叉树的遍历、序列化和反序列化、公共祖先、路径和这类题目的骨架代码非常值得提前写熟练。动态规划则没有太多模板可写但你可以把常见的DP状态定义套路记录下来比如背包问题、最长递增子序列、编辑距离等刷题时对照复盘。4.2 推荐的刷题顺序Hot100的题目顺序并不是按难度排的有些人打开列表从头开始刷刷到hard题容易心态爆炸。我更建议按知识点分块刷。下面是我验证过比较顺的刷题顺序哈希表与数组类先刷两数之和、三数之和、字母异位词分组、最长连续序列。这类题思路直接能快速建立信心。链表类反转链表、合并两个有序链表、环形链表、LRU缓存。链表题非常适合在本地模板里跑因为你可以自己构造链表并打印每一步状态。双指针与滑动窗口移动零、盛最多水的容器、无重复字符的最长子串。这类题刷起来很快适合穿插在其他板块之间调节节奏。二叉树类二叉树的中序遍历、最大深度、层序遍历、验证二叉搜索树、最近公共祖先。二叉树题在本地跑的体验提升最明显因为你可以直观地构建一棵树并验证结果。回溯与搜索全排列、子集、组合总和、岛屿数量。回溯题的模板代码相对固定强烈建议背下框架。动态规划爬楼梯、打家劫舍、最长回文子串、编辑距离。DP题最适合把状态转移方程写在注释里方便复盘。困难串讲接雨水、柱状图中最大的矩形、滑动窗口最大值、正则表达式匹配。这些题放在有基础以后再做体会完全不一样。每个板块集中刷完以后我会在本地模板里新建一个SUMMARY.md把每道题的核心思路、时间复杂度和当时踩的坑记下来。这个笔记的复利效应非常强面试前翻一遍比重新刷一遍更高效。4.3 高频困难题的本地调试技巧Hot100里的困难题像接雨水、柱状图中最大的矩形、寻找两个正序数组的中位数、正则表达式匹配这种题目在网页上写一次跑不过就容易烦躁。在本地则可以把问题拆得更细。以接雨水这道题为例最经典的做法是双指针或者单调栈。本地调试时可以分成三个小步骤先写一个暴力版按列计算每个位置能接多少水再优化成双指针版最后用单调栈版对比结果。三个版本放在同一个类里输出到控制台对比很容易验证自己的优化是否保持正确。另一个技巧是打印中间状态。接雨水需要理解当前位置能接多少水取决于左右两侧最大高度的较小值肉眼很难直接看出问题。我会在代码里临时加一个printHeights方法把原始数组、左侧最大高度数组、右侧最大高度数组都打出来一眼就能定位到哪一步计算错了。调试完再把打印代码删掉。这种调试方式在网页端做起来就很别扭但在本地非常顺手。5. 本地运行通过提交还报错常见问题排查实录5.1 环境变量与编译报错的经典场景配置完JDK后第一个常见报错就是javac: 无法找到或加载主类。这个不是环境变量问题而是你编译时没有指定正确的类路径。在IDEA里一般不遇到但在命令行手动编译时经常遇到。解决办法很简单编译时指定输出目录javac -d out src/template/*.java src/solution/S0001_TwoSum.java java -cp out solution.S0001_TwoSum第二个常见问题是IDEA的Project Structure没有把src目录标记为Sources导致所有类都显示红色找不到。新导入项目的时候IDEA偶尔不会自动识别。右键src目录选择Mark Directory as Sources Root就能解决。还有一个很隐蔽的编码问题。如果你的Windows系统默认编码是GBK而代码文件里有中文字符注释编译的时候可能报编码GBK的不可映射字符。这种情况我建议把IDEA的全局编码设置成UTF-8具体操作用的是File - Settings - Editor - File Encodings把Global Encoding和Project Encoding都改成UTF-8。在命令行编译时可以加上-encoding UTF-8参数javac -encoding UTF-8 -d out src/solution/S0001_TwoSum.java这个坑几乎每个Windows刷题的人都会碰到提前处理能省很多时间。5.2 本地通过但力扣提交失败的典型原因这类问题通常不是算法逻辑错了而是代码和力扣的运行环境不对齐。最典型的有三个。第一类名问题。力扣要求提交的类名是题目给定的大多数是Solution也有少数是Main。如果你本地类名叫S0042_TrapRainWater复制代码到力扣时一定要把类名改成Solution。我本地通常把核心逻辑写在一个内部类Solution里复制的时候直接复制这个内部类的全部内容就不会有问题。第二输入输出的残留代码。有些题目的核心方法里如果不小心写了System.out.println在力扣的判题环境下是没有问题的但如果你把解析输入的那些代码一并提交了就会编译报错。所以提交前检查一下粘贴的代码只包含题目里给出的方法签名和必要的数据结构定义。第三静态方法与非静态方法的混淆。力扣的大多数题目要求写实例方法比如public int[] twoSum(int[] nums, int target)。如果你在本地为了调用方便把它定义成了static提交后就会提示无法从静态上下文中引用。建议核心逻辑一律不写static本地调用的时候新建对象即可避免提交时忘记修改。另外还有一类比较隐蔽的问题就是默认包问题。IDEA里如果类没有放在任何包名下编译时在default package中提交到力扣通常没问题但本地多个文件都在默认包里时类名冲突的风险很大。我的建议是本地一律不用包名或者统一用一个包名比如leetcode但提交时删掉package语句就好。如果你用包名又忘记了删提交会直接编译失败。我最开始就栽过这个跟头后来索性本地也不用包名就靠文件名区别效果也不错。5.3 刷题效率提升的三个小习惯第一个习惯是给每个题目类保留一个testCases方法。不要每次都在main里手改测试用例写一个方法把多组输入和期望输出存成数组跑起来一次验证多个场景。比如两数之和我至少会测四组输入普通情况、目标值在负数场景、无解情况、数组只有两个元素。这样提交前就能覆盖大部分边界条件。第二个习惯是模板代码和解题代码分离。我上面提到的template包就是干这个的平时不轻易改动。解题代码放在solution包下模板代码稳定以后你已经默认它的正确性不需要每次刷题都重新验证。这个分离能大幅降低心智负担。第三个习惯定期回看和重构。Hot100刷到50题左右回看前20题你会发现很多当时的题解写得又长又乱。这时候用已经积累的新思路重构一遍老代码不要怕浪费时间去修改其实这个过程的成长效率非常高。因为相同的题你站在另一个高度重新审视对思路的巩固效果远胜过盲目开新题。6. 关于模板本身的持续迭代建议本地模板不是一次搭好就完事的它会随着你刷题数量的增加而逐渐丰富。举个例子刷到LRU缓存题的时候你可能会想写一个通用的双向链表模板刷到并查集的题目时你会需要一个通用的并查集类。这些工具代码都值得沉淀到template包里。我后来还在模板里加了一个Timer工具类用来简单统计每个算法的耗时public class Timer { private long start; public void start() { start System.currentTimeMillis(); } public void stopAndPrint(String label) { System.out.println(label : (System.currentTimeMillis() - start) ms); } }刷到性能敏感题的时候本地能直观看到两个版本的耗时差异比如排序算法、KMP字符串匹配这种感觉比只看力扣的复杂度分析要真实得多。还有一个值得做的扩展就是把Hot100中常见题型的模板代码抽成一个速查手册这个手册不一定要打印出来放在模板工程的README.md里就行。内容包括链表反转的递归与迭代写法、快慢指针模板、二叉树前中后序遍历的模板、回溯题目的框架模板、背包类DP的模板、二分查找的模板。这些模板看着简单但在面试高度紧张的状态下能快速唤醒记忆非常顶用。我一直觉得力扣刷题的核心不是题量而是形成一套稳定的个人方法论。本地模板就是这个方法论的载体。它逼着你把输入输出、数据结构定义、测试用例这些杂事规范化让你每次打开一个新题目都能把注意力放在最核心的算法设计上。把这些准备工作提前做完以后你再回头看他Hot100里的题目会发现思路的负担轻了很多剩下的主要就是算法的积累和熟练度。最后再说一个我个人的经验。模板这个东西贵精不贵多不要一开始就追求把所有东西都写完。先把我说的ListNode、TreeNode、ArrayParser和每个类里的main方法跑通然后就正式开始刷题。刷到哪道题觉得这个数据结构/工具方法好常用再回头往模板里补。这样你的模板始终是自然生长出来的每部分都确保有用而不是一堆用不上纯摆设的代码。
返回列表