Java LeetCode Hot100 刷题学习指南
刷题这件事,最怕的不是题目难,而是漫无目的地刷。本文档将带你从零基础开始,用六天时间掌握 Hot100 中最核心的算法套路和模板,让你真正做到"刷一题会一类"。
一、学习方法论:先背模板,再谈理解
作为新手,你的目标不是"发明算法",而是"识别题型并套用模板"。记住:
80% 的算法题 = 固定模板 + 变量替换
正确的学习顺序:
- 背模板:把核心代码写到肌肉记忆
- 抄题目:照葫芦画瓢完成第一道题
- 理解原理:搞清楚模板为什么能解决问题
- 举一反三:用同一模板解决类似问题
二、Day 1:数组 + 哈希表 + 双指针(基础打底)
这是最基础也最重要的一天,几乎所有题目都会用到这些技巧。
2.1 数组(Array)
数组是一切的起点,记住一个遍历模板就够了:
// 正序遍历for(inti=0;i<nums.length;i++){// 处理 nums[i]}// 倒序遍历(用于原地修改)for(inti=nums.length-1;i>=0;i--){// 处理 nums[i]}前缀和(PreSum)—— 快速算区间和
int[]pre=newint[nums.length+1];for(inti=0;i<nums.length;i++){pre[i+1]=pre[i]+nums[i];}// 求 nums[i] 到 nums[j] 的和intsum=pre[j+1]-pre[i];差分数组(Diff)—— 区间统一修改
int[]diff=newint[nums.length];diff[0]=nums[0];for(inti=1;i<nums.length;i++){diff[i]=nums[i]-nums[i-1];}// 给 [l, r] 加 valdiff[l]+=val;if(r+1<diff.length)diff[r+1]-=val;记忆口诀:区间统一加,差分两头掐。左端加,右端减,最后前缀和还原。
2.2 哈希表(HashMap)
哈希表的核心是"空间换时间",把 O(n) 的查找变成 O(1)。
基础操作(必背)
Map<Integer,Integer>map=newHashMap<>();map.put(key,value);// 放数据map.get(key);// 取数据(不存在返回 null)map.containsKey(key);// 判断有没有map.getOrDefault(key,0);// 取数据,没有就给默认值(高频!)三种高频用法
| 用法 | 模板 | 典型题 |
|---|---|---|
| 计数 | map.put(num, map.getOrDefault(num, 0) + 1) | 两数之和、字母异位词 |
| 存索引 | map.put(nums[i], i) | 两数之和 |
| 分组 | map.computeIfAbsent(key, k -> new ArrayList<>()).add(value) | 字母异位词分组 |
重点:
computeIfAbsent是 Java 8 新增方法,当 key 不存在时才创建新对象,避免重复创建。
2.3 双指针(Two Pointers)
双指针的本质是"用两个变量代替两层循环",把 O(n²) 优化成 O(n)。
快慢指针(原地修改)
intslow=0;for(intfast=0;fast<nums.length;fast++){if(nums[fast]!=val){nums[slow++]=nums[fast];}}returnslow;记忆口诀:快指针找非零,慢指针填坑。
对撞指针(有序数组)
intleft=0,right=nums.length-1;while(left<right){intsum=nums[left]+nums[right];if(sum==target)returnnewint[]{left+1,right+1};elseif(sum<target)left++;elseright--;}滑动窗口(进阶)
Map<Character,Integer>window=newHashMap<>();intleft=0,right=0;while(right<s.length()){charc=s.charAt(right);right++;window.put(c,window.getOrDefault(c,0)+1);while(需要收缩窗口){chard=s.charAt(left);left++;window.put(d,window.get(d)-1);}}2.4 Day 1 必刷题目
| 题目 | 类型 | 难度 |
|---|---|---|
| 1. 两数之和 | 哈希表 | 🟢 |
| 283. 移动零 | 快慢指针 | 🟢 |
| 242. 有效的字母异位词 | 哈希表 | 🟢 |
| 11. 盛最多水的容器 | 对撞指针 | 🟡 |
| 15. 三数之和 | 双指针 + 去重 | 🟡 |
| 3. 无重复字符的最长子串 | 滑动窗口 | 🟡 |
三、Day 2:链表(代码短但易出错)
链表是面试必考项,核心是指针操作和边界处理。
3.1 链表结构
classListNode{intval;ListNodenext;ListNode(intx){val=x;}}3.2 五大套路
套路一:虚拟头节点(删除题必备)
ListNodedummy=newListNode(-1);dummy.next=head;ListNodecur=dummy;为什么要用虚拟头?避免处理头节点为空的特殊情况。
套路二:反转链表(面试之王)
ListNodeprev=null;ListNodecur=head;while(cur!=null){ListNodenext=cur.next;cur.next=prev;prev=cur;cur=next;}returnprev;记忆口诀:保存下一个,反转指针,移动 prev 和 cur。
套路三:快慢指针(找中点/判环)
ListNodeslow=head,fast=head;while(fast!=null&&fast.next!=null){slow=slow.next;fast=fast.next.next;}// slow 就是中点套路四:删除倒数第 N 个节点
ListNodedummy=newListNode(0,head);ListNodefast=dummy,slow=dummy;for(inti=0;i<n;i++)fast=fast.next;while(fast.next!=null){fast=fast.next;slow=slow.next;}slow.next=slow.next.next;returndummy.next;3.3 Day 2 必刷题目
| 题目 | 类型 | 难度 |
|---|---|---|
| 206. 反转链表 | 指针翻转 | 🟢 |
| 21. 合并两个有序链表 | 递归/迭代 | 🟢 |
| 19. 删除链表的倒数第 N 个节点 | 快慢指针 | 🟡 |
| 141. 环形链表 | 快慢指针 | 🟢 |
| 25. K 个一组翻转链表 | 进阶 | 🔴 |
四、Day 3:栈 / 队列 / 字符串 / 滑动窗口
4.1 栈(Stack)—— 后进先出
Stack<Integer>stack=newStack<>();stack.push(x);// 入栈stack.pop();// 出栈stack.peek();// 查看栈顶stack.isEmpty();// 是否为空经典应用:有效的括号
Stack<Character>stack=newStack<>();for(charc:s.toCharArray()){if(c=='(')stack.push(')');elseif(c=='{')stack.push('}');elseif(c=='[')stack.push(']');elseif(stack.isEmpty()||stack.pop()!=c)returnfalse;}returnstack.isEmpty();4.2 队列(Queue)—— 先进先出
Queue<Integer>queue=newLinkedList<>();queue.offer(x);// 入队queue.poll();// 出队queue.peek();// 查看队首4.3 字符串操作
s.charAt(i);// 获取第 i 个字符s.substring(i,j);// 截取 [i, j)s.toCharArray();// 转字符数组s.equals(other);// 比较内容(别用 ==)s.length();// 长度4.4 Day 3 必刷题目
| 题目 | 类型 | 难度 |
|---|---|---|
| 20. 有效的括号 | 栈 | 🟢 |
| 155. 最小栈 | 栈设计 | 🟢 |
| 739. 每日温度 | 单调栈 | 🟡 |
| 3. 无重复字符的最长子串 | 滑动窗口 | 🟡 |
| 76. 最小覆盖子串 | 滑动窗口 | 🔴 |
五、Day 4:二叉树 + DFS/BFS(重中之重)
二叉树是算法世界的"通用货币",掌握它几乎能解决一半的面试题。
5.1 二叉树结构
classTreeNode{intval;TreeNodeleft;TreeNoderight;TreeNode(intx){val=x;}}5.2 四种遍历方式
前序遍历(根 → 左 → 右)
voidpreorder(TreeNoderoot){if(root==null)return;System.out.println(root.val);preorder(root.left);preorder(root.right);}中序遍历(左 → 根 → 右)
voidinorder(TreeNoderoot){if(root==null)return;inorder(root.left);System.out.println(root.val);inorder(root.right);}后序遍历(左 → 右 → 根)
voidpostorder(TreeNoderoot){if(root==null)return;postorder(root.left);postorder(root.right);System.out.println(root.val);}层序遍历(BFS)
voidlevelOrder(TreeNoderoot){Queue<TreeNode>queue=newLinkedList<>();queue.offer(root);while(!queue.isEmpty()){TreeNodenode=queue.poll();System.out.println(node.val);if(node.left!=null)queue.offer(node.left);if(node.right!=null)queue.offer(node.right);}}5.3 三大核心思想
思想一:递归 = 相信它能搞定子树
intmaxDepth(TreeNoderoot){if(root==null)return0;returnMath.max(maxDepth(root.left),maxDepth(root.right))+1;}思想二:回溯(路径问题)
path.add(root.val);dfs(root.left);path.remove(path.size()-1);思想三:分治
returnmerge(leftResult,rightResult);5.4 Day 4 必刷题目
| 题目 | 类型 | 难度 |
|---|---|---|
| 104. 二叉树的最大深度 | 递归 | 🟢 |
| 100. 相同的树 | 递归比较 | 🟢 |
| 101. 对称二叉树 | 递归比较 | 🟢 |
| 102. 二叉树的层序遍历 | BFS | 🟡 |
| 236. 二叉树的最近公共祖先 | 后序遍历 | 🟡 |
六、Day 5:二分查找 + 回溯 + 贪心
6.1 二分查找
二分查找的核心是"每次排除一半",前提是数组有序。
intleft=0,right=nums.length-1;while(left<=right){intmid=left+(right-left)/2;// 防止溢出if(nums[mid]==target)returnmid;elseif(nums[mid]<target)left=mid+1;elseright=mid-1;}return-1;记忆口诀:左闭右闭
<=,左闭右开<,mid 别溢出。
6.2 回溯(Backtracking)
回溯的本质是"尝试所有可能,不行就撤回"。
voidbacktrack(路径,选择列表){if(满足结束条件){result.add(路径);return;}for(选择:选择列表){做选择;backtrack(路径,选择列表);撤销选择;}}6.3 贪心(Greedy)
贪心的核心是"每一步都选最优,不回头"。
// 例子:跳跃游戏intmaxReach=0;for(inti=0;i<nums.length;i++){if(i>maxReach)returnfalse;maxReach=Math.max(maxReach,i+nums[i]);}returntrue;6.4 Day 5 必刷题目
| 题目 | 类型 | 难度 |
|---|---|---|
| 33. 搜索旋转排序数组 | 二分 | 🟡 |
| 704. 二分查找 | 基础 | 🟢 |
| 46. 全排列 | 回溯 | 🟡 |
| 78. 子集 | 回溯 | 🟡 |
| 55. 跳跃游戏 | 贪心 | 🟡 |
七、Day 6:动态规划(DP)
动态规划是算法中的"核武器",掌握它能解决最难的问题。
7.1 DP 解题框架
- 定义状态:
dp[i]表示什么? - 状态转移方程:
dp[i] = f(dp[i-1], dp[i-2], ...) - 初始化:
dp[0],dp[1]等于什么? - 遍历顺序:从前到后还是从后到前?
7.2 常见 DP 类型
一维 DP
// 爬楼梯:dp[i] = dp[i-1] + dp[i-2]int[]dp=newint[n+1];dp[0]=1;dp[1]=1;for(inti=2;i<=n;i++){dp[i]=dp[i-1]+dp[i-2];}二维 DP
// 最小路径和int[][]dp=newint[m][n];dp[0][0]=grid[0][0];for(inti=0;i<m;i++){for(intj=0;j<n;j++){dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];}}7.3 Day 6 必刷题目
| 题目 | 类型 | 难度 |
|---|---|---|
| 53. 最大子数组和 | 一维 DP | 🟢 |
| 70. 爬楼梯 | 一维 DP | 🟢 |
| 198. 打家劫舍 | 一维 DP | 🟡 |
| 62. 不同路径 | 二维 DP | 🟡 |
| 72. 编辑距离 | 二维 DP | 🔴 |
八、六天速通计划总结
| 天数 | 内容 | 核心模板 |
|---|---|---|
| Day 1 | 数组 + 哈希表 + 双指针 | 遍历、计数、快慢指针、滑动窗口 |
| Day 2 | 链表 | 反转、虚拟头、快慢指针 |
| Day 3 | 栈/队列/字符串 | 栈模拟、滑动窗口框架 |
| Day 4 | 二叉树 + DFS/BFS | 递归、BFS、后序遍历 |
| Day 5 | 二分 + 回溯 + 贪心 | 二分模板、回溯框架、贪心策略 |
| Day 6 | 动态规划 | DP 四步框架 |
九、常用方法速查
9.1 HashMap 常用方法
| 方法 | 说明 |
|---|---|
put(K key, V value) | 插入键值对 |
get(Object key) | 根据 key 获取 value |
containsKey(Object key) | 判断是否包含 key |
getOrDefault(Object key, V defaultValue) | 获取 value,不存在返回默认值 |
computeIfAbsent(K key, Function remappingFunction) | key 不存在时创建并返回 |
size() | 返回元素个数 |
9.2 数组常用操作
| 操作 | 代码 |
|---|---|
| 遍历 | for (int i = 0; i < nums.length; i++) |
| 长度 | nums.length |
| 复制 | Arrays.copyOf(nums, length) |
| 排序 | Arrays.sort(nums) |
| 查找 | Arrays.binarySearch(nums, target) |
9.3 字符串常用操作
| 操作 | 代码 |
|---|---|
| 长度 | s.length() |
| 获取字符 | s.charAt(i) |
| 截取 | s.substring(start, end) |
| 转数组 | s.toCharArray() |
| 比较 | s.equals(other) |
| 拼接 | s.concat(other)或s + other |
十、新手避坑指南
10.1 Java 泛型不支持基本类型
Map<int,int>map;// ❌ 错误!Map<Integer,Integer>map;// ✅ 正确10.2 字符串比较别用 ==
if(s1==s2){}// ❌ 比较引用if(s1.equals(s2)){}// ✅ 比较内容10.3 数组越界检查
// 正确做法if(i>=0&&i<nums.length){...}10.4 递归必须有终止条件
voiddfs(TreeNoderoot){if(root==null)return;// 终止条件dfs(root.left);dfs(root.right);}结语
刷题不是目的,而是手段。通过这六天的学习,你应该建立起一套完整的算法思维体系:看到题目能快速判断"该用什么模板",写出代码能保证"逻辑正确无 Bug"。
记住几个关键的"成功标志":
- 能在 5 分钟内默写反转链表代码
- 能在 10 分钟内写出两数之和的哈希解法
- 能在 15 分钟内完成二叉树的层序遍历
- 能独立分析出一道 DP 题的状态转移方程
当这些都做到时,你已经具备了应对大多数面试的能力。接下来的路还很长,但你已经迈出了最关键的一步。
算法之路,道阻且长,行则将至。