很多刚开始学Java算法的同学,或者想转行后端开发的程序员,常常会有这样的困惑:“明明代码能跑通,为什么一上机就懵?” 或者 “刷了几百道题,为什么面试还是被挂?”
其实,刷题不是靠“量”堆出来的,而是靠“体系”建出来的。今天我就结合自己带新人、做技术面试官的经验,把这套从LeetCode入门到剑指Offer进阶,再到资源选择的完整避坑指南,掰开了揉碎了讲给你听。咱们不整那些虚的,直接上干货。
一、 为什么你总是“眼高手低”?先解决心理和技术两个层面的坑
1.1 第一个大坑:只看不写,以为看懂了就是会了
这是新手最容易掉进去的陷阱。你在LeetCode上看题解,觉得“卧槽,这思路好妙,原来如此”,然后合上页面,自信满满地点下一题。结果到了自己写的时候,连while循环的边界都搞不清楚,或者根本不知道怎么把思路翻译成Java代码。
真相是: 算法的核心不是“想”,而是“写”和“调试”。
建议做法:
- 强制输出: 看完题解后,关掉页面,自己在本地IDE(IntelliJ IDEA或Eclipse)里重新写一遍。
- 注释为王: 每写一行代码,都要在旁边用中文注释清楚这一行在干什么。比如
// 移动左指针,因为当前值不满足条件。 - 调试能力: 学会用IDE的Debug功能,单步执行,观察变量变化。这才是真正理解代码逻辑的唯一途径。
1.2 第二个大坑:忽视边界条件,代码总是AC不了
Java刷题里,90%的报错不是因为逻辑错误,而是因为边界条件没考虑周全。比如数组为空、链表为空、只有一个元素、所有元素都相同、最大值最小值等极端情况。
例子: 一道经典的“两数之和”变体,要求找到数组中两个数的和等于目标值。如果你不考虑数组长度为1的情况,或者两个数是同一个元素(索引不能重复)的情况,代码就会出错。
避坑技巧: 在写代码前,先在心里过一遍这些情况:
- 输入为空(null或空数组)
- 输入只有一个元素
- 结果不存在(比如找不到目标值)
- 结果有多个(题目是否要求返回所有解)
- 数值溢出(尤其是涉及整数相乘或相加时)
二、 数据结构与算法:Java实现的核心重点
刷题不是裸奔,你需要熟练掌握Java中各种数据结构的API和底层实现。下面我按重要性排序,给你梳理几个必须吃透的考点。
2.1 数组与字符串:最基础也最容易出错
数组和字符串是几乎所有算法题的载体。在Java中,字符串是不可变的,这意味着每次拼接都会产生新对象,所以千万不要在循环里用+拼接字符串,要用StringBuilder。
实战案例:反转字符串(LeetCode 344)
class Solution {
public void reverseString(char[] s) {
int left = 0, right = s.length - 1;
while (left < right) {
// 交换元素
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
}
}
注意点:
- 交换操作要确保不越界。
- 使用双指针是处理对称结构(如回文、反转)的常用技巧。
2.2 链表:指针操作的噩梦
链表是面试中最常考的数据结构之一,因为它的操作涉及到大量的指针(在Java中是引用)操作,容易出错。你需要熟练掌握链表的增删改查,以及常见操作的写法。
实战案例:反转链表(LeetCode 206)
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode nextTemp = curr.next; // 暂存下一个节点
curr.next = prev; // 反转指针
prev = curr; // prev前移
curr = nextTemp; // curr前移
}
return prev; // prev成为新的头节点
}
}
避坑指南:
- 务必画图解: 链表反转涉及指针改动,一定要在纸上或IDE里画出来,理清
prev、curr、nextTemp三个指针的关系。 - 空指针异常: 检查
curr和curr.next是否为null。
2.3 栈与队列:单调栈是进阶利器
栈(LIFO)和队列(FIFO)是基础数据结构。但面试中更常考的是单调栈和单调队列,用于解决“下一个更大元素”、“滑动窗口最大值”等问题。
实战案例:每日温度(LeetCode 739)- 单调栈
class Solution {
public int[] dailyTemperatures(int[] temperatures) {
int n = temperatures.length;
int[] result = new int[n];
// 栈中存储的是索引,而不是温度值
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
// 当当前温度高于栈顶索引对应的温度时,弹出栈顶并计算天数差
while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) {
int index = stack.poll();
result[index] = i - index;
}
stack.push(i);
}
return result;
}
}
关键点:
- 单调栈的核心思想是:维护一个栈,使得栈内元素保持单调性(递增或递减)。
- 本题中,栈内存储的是索引,方便计算天数差。
2.4 哈希表:快速查找的神器
哈希表(HashMap)在Java中实现非常高效,平均时间复杂度O(1)。它常用于“两数之和”、“字母异位词”、“重复元素”等问题。
实战案例:两数之和(LeetCode 1)
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, 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[] {}; // 理论上不会执行到这里
}
}
注意点:
- 遍历时,先查找再插入,避免同一个元素被使用两次。
- 哈希表的
containsKey和get操作要区分清楚。
2.5 二叉树:递归思维的试金石
二叉树的遍历(前序、中序、后序、层序)是必考内容。递归写法简洁,但理解起来需要一定的思维转换能力。
实战案例:二叉树的最大深度(LeetCode 104)
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
// 递归计算左子树和右子树的最大深度
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
// 当前节点的最大深度 = 左右子树最大深度的最大值 + 1
return Math.max(leftDepth, rightDepth) + 1;
}
}
学习建议:
- 递归三要素: 1. 终止条件(root为null);2. 单步操作(计算左右子树深度);3. 返回值(最大深度)。
- 画图辅助: 画出一棵小树的递归调用栈,有助于理解。
三、 剑指Offer:从LeetCode到面试的跨越
LeetCode上的题目种类繁多,有些题目比较偏,或者难度极大,不适合初学者。而《剑指Offer》这本书(和其中的对应在线题目)则是专门为面试准备的,题目更经典、更贴近实际业务场景,难度梯度也更合理。
3.1 剑指Offer vs LeetCode:怎么选?
- LeetCode: 题库巨大,适合刷题练手,尤其是想冲击大厂高薪的同学,需要大量练习。
- 剑指Offer: 题目精炼,覆盖了面试中最常考的200+道题,适合入门和进阶,是面试准备的“标配”。
建议路线:
- 第一阶段(1-2周): 刷完剑指Offer中的前50道题,重点是数组、字符串、链表、栈、队列。
- 第二阶段(2-4周): 深入剑指Offer,完成树、动态规划、回溯、贪心等专题。
- 第三阶段(持续): 回到LeetCode,针对薄弱点进行专项训练,比如“动态规划”、“图论”等难点。
3.2 剑指Offer经典案例:旋转数组的最小数字
题目: 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组[3,4,5,1,2]为[1,2,3,4,5]的一个旋转,该数组的最小值为1。
思路: 这道题可以用二分查找解决,时间复杂度O(log n)。关键是要处理重复元素的情况。
class Solution {
public int minArray(int[] numbers) {
int left = 0, right = numbers.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (numbers[mid] > numbers[right]) {
// 最小值在mid的右边
left = mid + 1;
} else if (numbers[mid] < numbers[right]) {
// 最小值在mid的左边(包括mid)
right = mid;
} else {
// numbers[mid] == numbers[right],无法判断,缩小右边界
right--;
}
}
return numbers[left];
}
}
避坑点:
- 当
numbers[mid] == numbers[right]时,不能直接排除左半部分或右半部分,只能将right减1,逐步缩小范围。这是这道题的难点。
四、 优质资源推荐:书、视频、平台
工欲善其事,必先利其器。选好学习资源,能让你事半功倍。
4.1 书籍推荐
《剑指Offer(第2版)》- 何海涛
- 特点: 经典中的经典,题目覆盖全面,解答详细,代码是Java实现的(部分旧版是C++,请注意选择Java版或自行翻译)。
- 适合人群: 入门、进阶,准备面试的必备书籍。
《算法图解》- 巴尔加瓦
- 特点: 图文并茂,通俗易懂,非常适合零基础或想复习基础概念的同学。
- 适合人群: 初学者,建立算法直觉。
《算法4》- Sedgewick
- 特点: 内容非常全面,理论结合实践,代码质量高,是算法领域的权威教材。
- 适合人群: 想深入理解算法原理,而非仅仅刷题的同学。
4.2 视频课程推荐
B站 - 代码随想录(Carl)
- 特点: 讲解清晰,思路独到,有很多总结性的专题(如“动态规划总结篇”),非常适合应试。
- 搜索关键词: “代码随想录 算法”
慕课网 - 左程云《程序员代码面试指南》
- 特点: 左程云老师是业界知名的算法讲师,课程深入,有很多高频面试题的变种讲解。
- 适合人群: 有一定基础,想进一步提升的同学。
极客时间 - 王争《数据结构与算法之美》
- 特点: 理论扎实,讲解透彻,帮助建立完整的知识体系。
- 适合人群: 想系统学习数据结构与算法理论的同学。
4.3 刷题平台
LeetCode(力扣)
- 特点: 题目最多,社区活跃,有中文题解,适合日常刷题。
- 建议: 建立自己的“题库本”,记录做错的题,定期回顾。
牛客网
- 特点: 国内互联网大厂面试真题较多,有模拟面试功能,适合校招准备。
- 建议: 刷剑指Offer专项练习,参加公司的笔试模拟。
HihoCoder
- 特点: 题目质量高,有专门的算法训练营,适合进阶。
五、 给小朋友也能听懂的:算法就像搭积木
最后,我想用一个小比喻,让算法学习变得更亲切。
想象一下,你有一大堆不同形状和大小的积木(数据),现在你要把它们搭成一个漂亮的城堡(解决问题)。
- 数组就像是一排整齐的积木槽,你可以快速找到第几个积木,但想插入一个新的,就得把后面的都挪开。
- 链表就像是一串有密码的宝藏盒,每个盒子里只放着一张纸条,告诉你下一个宝藏盒在哪。你想找某个宝藏,就得从头开始,顺着纸条一个一个找。
- 栈就像是一叠盘子,你只能从最上面拿盘子,或者把盘子放在最上面。这叫“后进先出”。
- 队列就像是在超市排队,先来的人先结账,这叫“先进先出”。
- 二叉树就像是一个家族树,每个人有两个孩子(左孩子和右孩子),你可以从上往下找(递归),也可以一层一层地找(层序遍历)。
- 哈希表就像是一个带有编号的储物柜,你可以通过编号(哈希值)直接找到你的东西,不用一个个找。
刷题的过程,就是不断熟悉这些“积木”的特性,然后练习如何把它们组合起来,搭出各种各样的“城堡”。 刚开始你可能会搭得慢,甚至搭错,但只要你多练习,多总结经验,总有一天你能飞速地搭出复杂的城堡!
六、 结语:坚持与复盘,比刷题数量更重要
最后,送给大家一句话:“刷题不是目的,提升思维能力才是。”
不要追求一天刷多少题,而是要追求每一道题都真正理解。做完一道题,问自己几个问题:
- 这道题考察的是什么知识点?
- 我的解法有没有更优的?
- 边界条件考虑全了吗?
- 如果面试官追问,我能讲清楚吗?
把这几百道题真正吃透,你的算法能力会有质的飞跃。祝你刷题愉快,早日拿到心仪的Offer!
