Java算法学习从LeetCode刷题到面试实战零基础入门经典书籍与在线资源推荐
为什么你要学算法
说句实在话,算法这东西,刚开始接触的时候确实让人头疼。我第一次看到”动态规划”这四个字的时候,脑子里全是问号。但说实话,只要你迈过那道坎,会发现算法其实挺有意思的,而且对你找工作真的特别有帮助。
国内大厂面试,不管是阿里巴巴、腾讯、字节跳动还是美团,几乎都要考算法题。不是故意刁难你,而是算法题能最快看出一个人的逻辑思维能力和代码功底。今天咱们就好好聊聊,零基础怎么从算法小白变成能面试手撕代码的实力派。
零基础入门:别急着刷题,先把基础打牢
第一个坑:很多人一上来就刷LeetCode
我见过太多人,连数组排序都没搞明白,就直接打开LeetCode开始刷题,结果第一题就卡住了。”两数之和”看着简单,但如果连HashMap怎么用的都不清楚,怎么做?
所以第一步,先把Java基础语法和数据结构搞清楚。这部分不用花太多时间,但必须扎实。
你需要掌握的Java基础包括:
- 基本数据类型和运算符
- 条件语句和循环语句
- 数组、字符串的基本操作
- 面向对象的基本概念(类、对象、继承、多态)
数据结构:算法的地基
算法题90%都在操作数据结构,所以数据结构必须滚瓜烂熟。
数组和字符串是最基础的,几乎每道题都会用到。你需要知道:
- 数组的创建和遍历
- 字符串的常用方法(
charAt、substring、split、indexOf等) - 字符串反转、回文判断等经典操作
代码示例:
// 字符串反转的经典写法
public String reverseString(String s) {
char[] chars = s.toCharArray();
int left = 0, right = chars.length - 1;
while (left < right) {
char temp = chars[left];
chars[left] = chars[right];
chars[right] = temp;
left++;
right--;
}
return new String(chars);
}
链表是面试高频考点,你必须熟悉:
- 单向链表的创建和遍历
- 反转链表(这个题必会)
- 快慢指针找中间节点
- 环检测
// 反转链表 - 面试常考
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode current = head;
while (current != null) {
ListNode nextTemp = current.next;
current.next = prev;
prev = current;
current = nextTemp;
}
return prev;
}
栈和队列也很重要,尤其是栈,递归的本质就是栈。
- 栈:先进后出(LIFO)
- 队列:先进先出(FIFO)
- 双端队列:两头都能进出
// 用栈实现队列
class MyQueue {
private Stack<Integer> stack1 = new Stack<>();
private Stack<Integer> stack2 = new Stack<>();
public void push(int x) {
stack1.push(x);
}
public int pop() {
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
树和二叉树是进阶必学的内容:
- 二叉树的遍历(前序、中序、后序、层序)
- 二叉搜索树(BST)的性质
- 递归解法的思维模式
// 二叉树三种遍历方式
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
// 前序遍历
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
result.add(root.val);
result.addAll(preorderTraversal(root.left));
result.addAll(preorderTraversal(root.right));
return result;
}
// 中序遍历
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
result.addAll(inorderTraversal(root.left));
result.add(root.val);
result.addAll(inorderTraversal(root.right));
return result;
}
图相对难一些,但面试中经常考 BFS 和 DFS。
// BFS 遍历图
public List<Integer> bfs(int start, int n, List<List<Integer>> graph) {
List<Integer> result = new ArrayList<>();
boolean[] visited = new boolean[n];
Queue<Integer> queue = new LinkedList<>();
queue.offer(start);
visited[start] = true;
while (!queue.isEmpty()) {
int current = queue.poll();
result.add(current);
for (int neighbor : graph.get(current)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.offer(neighbor);
}
}
}
return result;
}
算法思想:掌握套路比刷多少题更重要
很多人刷题刷了几百道,面试还是不会,原因就在于没有总结算法套路。下面这些思想,你必须理解并会运用。
1. 双指针
双指针适合处理数组、链表类问题,特别是排序数组的操作。
// 二分查找 - 双指针的经典应用
public int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
2. 滑动窗口
滑动窗口适合处理连续子数组、子字符串问题。
// 滑动窗口 - 找最长无重复字符子串
public int lengthOfLongestSubstring(String s) {
Set<Character> set = new HashSet<>();
int left = 0, maxLen = 0;
for (int right = 0; right < s.length(); right++) {
while (set.contains(s.charAt(right))) {
set.remove(s.charAt(left));
left++;
}
set.add(s.charAt(right));
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
3. 递归和分治
递归是算法的基础思维,分治是把大问题拆成小问题。
// 归并排序 - 分治的经典应用
public int[] mergeSort(int[] arr) {
if (arr.length <= 1) return arr;
int mid = arr.length / 2;
int[] left = mergeSort(Arrays.copyOfRange(arr, 0, mid));
int[] right = mergeSort(Arrays.copyOfRange(arr, mid, arr.length));
return merge(left, right);
}
private int[] merge(int[] left, int[] right) {
int[] result = new int[left.length + right.length];
int i = 0, j = 0, k = 0;
while (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
result[k++] = left[i++];
} else {
result[k++] = right[j++];
}
}
while (i < left.length) result[k++] = left[i++];
while (j < right.length) result[k++] = right[j++];
return result;
}
4. 动态规划
动态规划是面试难点,但掌握套路后其实不难。核心就是找到状态转移方程。
// 爬楼梯问题 - 最基础的动态规划
public int climbStairs(int n) {
if (n <= 2) return n;
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 优化空间复杂度 - 滚动数组
public int climbStairsOptimized(int n) {
if (n <= 2) return n;
int prev2 = 1;
int prev1 = 2;
int current = 0;
for (int i = 3; i <= n; i++) {
current = prev1 + prev2;
prev2 = prev1;
prev1 = current;
}
return prev1;
}
经典书籍推荐:选对书,事半功倍
书籍这东西,选对了比什么方法都管用。下面这些书我真心推荐,都是经过很多程序员验证的经典。
入门级:《Java算法与数据结构精解》
这本书特别适合零基础的同学。它用Java语言讲解算法,例子通俗易懂,代码完整可直接运行。每章后面都有练习题,而且答案也给得很详细。
书里从最基础的排序算法讲起,比如冒泡排序、插入排序、选择排序,然后慢慢过渡到更高级的算法。这种循序渐进的方式,不会让你一开始就被吓跑。
// 书中冒泡排序的实现,很清晰
public static void bubbleSort(int[] arr) {
int n = arr.length;
boolean swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true;
}
}
// 如果没有发生交换,说明已经有序
if (!swapped) break;
}
}
进阶级:《算法》第4版(Sedgewick著)
这本书是算法领域的经典中的经典,被很多大学用作教材。虽然作者是外国人,但写得非常清晰,而且有很多图示帮助理解。
唯一的缺点是英文原版,但国内有中文翻译版。书里用的是Java语言,代码质量很高,注释也很详细。
// 书中归并排序的实现,优雅而清晰
public class Merge {
private static void merge(int[] a, int[] aux, int lo, int mid, int hi) {
// 复制到aux数组
for (int k = lo; k <= hi; k++) {
aux[k] = a[k];
}
// 归并回a数组
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (aux[j] < aux[i]) a[k] = aux[j++];
else a[k] = aux[i++];
}
}
}
实战级:《剑指Offer》
这本书专门针对IT公司面试算法题,里面的题目大部分都是真实面试题。对于准备面试的同学来说,这本书几乎是必看的。
不过要注意,这本书的题有一定难度,建议先把前面的基础打牢了再来看。书里的解法通常会给出多种思路,从暴力解法到优化解法,很能启发思维。
// 书中"二维数组中的查找"的解法
public class Solution {
public boolean find(int target, int[][] array) {
if (array == null || array.length == 0 || array[0].length == 0) {
return false;
}
int rows = array.length;
int cols = array[0].length;
// 从右上角开始查找
int row = 0;
int col = cols - 1;
while (row < rows && col >= 0) {
if (array[row][col] == target) {
return true;
} else if (array[row][col] > target) {
col--; // 当前值太大,向左移动
} else {
row++; // 当前值太小,向下移动
}
}
return false;
}
}
进阶挑战:《算法导论》
这本书是算法领域的”圣经”,但难度较高。如果你已经有一定基础,想挑战自己,可以看看。不过不建议零基础直接读这本书,会打击信心。
书里的数学证明比较多,适合深入理解算法原理。对于面试来说,其实不需要读完整本书,挑几章重点看就够了。
在线资源推荐:免费的宝藏很多
LeetCode官方网站
刷题首选LeetCode。它的界面简洁,题目质量高,而且有社区讨论,遇到不会的题可以去参考别人的解法。
建议刷题顺序:
- 先刷”面试经典150题”(LeetCode官方整理的)
- 按标签刷:数组、字符串、链表、树、动态规划等
- 按难度刷:先简单,再中等,最后困难
简单题推荐顺序:
两数之和 → 反转字符串 → 有效括号 → 合并两个有序数组 → 二叉树的最大深度
中等题推荐顺序:
两数相加 → 无重复字符的最长子串 → 旋转图像 → 盛最多水的容器 → 三数之和
牛客网
牛客网也是国内很受欢迎的刷题平台,有很多国内公司的真题。它的面试经验区也很棒,可以看看别人分享的面试经历。
// 牛客网上的经典题:跳台阶
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
System.out.println(jumpFloor(n));
}
public static int jumpFloor(int target) {
if (target <= 2) return target;
int prev2 = 1;
int prev1 = 2;
int current = 0;
for (int i = 3; i <= target; i++) {
current = prev1 + prev2;
prev2 = prev1;
prev1 = current;
}
return prev1;
}
}
编程随想
这个网站有很多算法解析文章,用中文写的,很适合国内程序员。文章质量很高,会详细讲解解题思路和代码实现。
GitHub上的算法仓库
GitHub上有很多优秀的算法仓库,比如:
labuladong/fucking-algorithm:算法小抄,非常实用doocs/leetcode:LeetCode解题大全,有详细注释
// 从labuladong的仓库里摘的滑动窗口模板
void slidingWindow(String s) {
Map<Character, Integer> window = new HashMap<>();
int left = 0, right = 0;
int res = Integer.MAX_VALUE; // 记录结果
while (right < s.length()) {
char c = s.charAt(right);
right++;
// 进行窗口内数据的一系列更新
window.put(c, window.getOrDefault(c, 0) + 1);
// 判断窗口是否需满足某种需求
while (window needs shrink) {
char d = s.charAt(left);
left++;
// 进行窗口内数据的一系列更新
window.put(d, window.getOrDefault(d, 0) - 1);
}
// 更新结果
res = Math.min(res, right - left);
}
return res;
}
B站视频资源
B站上有很多优质的算法课程,比如:
- 代码随想录的算法视频
- 小浩图解算法
- 韩顺平的数据结构与算法
视频教程的好处是可以跟着老师的思路一步步理解,比自己看书效率高很多。
面试实战:从刷题到拿Offer
面试前的准备
刷题不要贪多,重在理解和总结。我见过很多人刷了500道题,但每次面试还是不会,原因就是没有总结套路。
建议的学习节奏:
- 第一阶段(1-2周):学习基础数据结构和算法思想,刷简单题50道左右
- 第二阶段(2-4周):系统刷题,按标签刷,每种题型刷20-30道
- 第三阶段(2-3周):刷中等难度题,重点攻克动态规划、树、图等难点
- 第四阶段(面试前1周):复习错题,做模拟面试
面试时如何解题
面试时遇到算法题,不要急着写代码,先和面试官沟通:
- 确认题目要求,问清楚边界条件
- 说出你的思路,先给一个暴力解法
- 思考如何优化,给出最优解法
- 写代码时注意命名规范和代码风格
- 写完后自己举几个例子验证
// 面试时的标准解题格式
public class Solution {
/**
* 题目:两数之和
* 思路:使用HashMap存储已遍历的数字,时间复杂度O(n)
* @param nums 整数数组
* @param target 目标和
* @return 两个数的下标
*/
public int[] twoSum(int[] nums, int target) {
// 边界条件检查
if (nums == null || nums.length < 2) {
return new int[0];
}
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[0]; // 题目保证有解,这行不会执行到
}
}
常见面试题型总结
数组类:
- 两数之和、三数之和
- 接雨水
- 股票买卖系列
链表类:
- 反转链表
- 合并两个有序链表
- 链表中环的检测
树类:
- 二叉树的遍历
- 二叉树的最大深度
- 验证二叉搜索树
动态规划类:
- 爬楼梯
- 最长递增子序列
- 0-1背包问题
给零基础同学的几点建议
不要急于求成:算法学习需要时间积累,不要指望几天就能搞定。每天花1-2小时,坚持三个月,效果会很明显。
理解比刷题更重要:不要盲目刷多少题,而是要真正理解每道题的思路和解题方法。一道题弄懂,比十道题囫囵吞枣强得多。
动手写代码:看懂了不代表会写了,一定要自己动手敲代码。代码敲得多了,手感自然就来了。
学会复盘:每周花点时间回顾之前做过的题,特别是做错的题。定期复盘能加深记忆,也能发现规律。
保持心态:遇到难题很正常,不要因此否定自己。每个程序员都是从新手过来的,多练习,多思考,总能学会的。
结语
学习算法这条路,说难也难,说简单也简单。难在需要持续投入时间和精力,简单在只要方法对,每个人都能学会。
记住,不要和别人比进度,每个人都有自己的节奏。找到适合自己的学习方法,坚持下去,面试时你一定能从容应对。
如果你现在还是零基础,不要害怕。从今天开始,每天刷一道简单题,三个月后你会感谢现在的自己。加油!
