算法能力提升
目录
一、为什么要提高算法能力
1.1 算法能力的重要性
对职业发展的影响:
- 面试必备:一线大厂(阿里、字节、腾讯)必考算法题
- 技术晋升:P6→P7 需要更强的算法和架构能力
- 问题解决:复杂业务场景需要算法思维
- 代码质量:好的算法意识能写出更优雅的代码
- 竞争力:算法好的工程师在求职市场更有优势
实际工作中的应用:
业务场景 → 需要的算法能力
────────────────────────────────
海量数据处理 → 哈希、位运算、分治
推荐系统 → 图算法、动态规划
搜索功能 → 字符串匹配、前缀树
实时计算 → 滑动窗口、双指针
缓存设计 → LRU/LFU(链表+哈希)
限流降级 → 滑动窗口、令牌桶
1.2 算法能力的层次
| 层次 | 能力描述 | 典型表现 | LeetCode水平 |
|---|---|---|---|
| Level 0 | 基础薄弱 | 不会用数组、链表 | 0-50题 |
| Level 1 | 入门 | 会基本遍历、简单逻辑 | 50-150题 |
| Level 2 | 掌握基础 | 熟悉常见数据结构和算法 | 150-300题 |
| Level 3 | 熟练 | 能独立解决中等难度问题 | 300-500题 |
| Level 4 | 精通 | 快速解决大部分问题 | 500-800题 |
| Level 5 | 专家 | 能优化到最优解,竞赛水平 | 800+题 |
目标设定:
- 求职(大厂):至少达到 Level 3(300题)
- 晋升(高级工程师):Level 4(500题)
- 技术专家:Level 5(持续刷题)
二、算法学习路线图
2.1 完整学习路径(12周计划)
Week 1-2: 基础数据结构
↓
Week 3-4: 基础算法(排序、查找)
↓
Week 5-6: 进阶数据结构(树、图)
↓
Week 7-8: 高级算法(DP、贪心)
↓
Week 9-10: 专题突破
↓
Week 11-12: 刷题与总结
2.2 详细学习计划
阶段一:基础数据结构(Week 1-2)
目标: 熟练掌握基础数据结构的实现和应用
学习内容:
1. 数组(Array)
- 核心操作:访问、插入、删除、查找
- 时间复杂度:O(1) 访问,O(n) 插入/删除
- 经典问题:
- 两数之和
- 数组去重
- 合并区间
- 螺旋矩阵
// 典型题:两数之和
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[] {};
}
2. 链表(LinkedList)
- 单链表、双向链表、循环链表
- 核心操作:插入、删除、反转、合并
- 经典问题:
- 反转链表
- 环形链表检测
- 合并两个有序链表
- LRU缓存
// 典型题:反转链表
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
// LRU缓存实现
class LRUCache {
class Node {
int key, value;
Node prev, next;
Node(int k, int v) { key = k; value = v; }
}
private Map<Integer, Node> map = new HashMap<>();
private Node head = new Node(0, 0);
private Node tail = new Node(0, 0);
private int capacity;
public LRUCache(int capacity) {
this.capacity = capacity;
head.next = tail;
tail.prev = head;
}
public int get(int key) {
if (!map.containsKey(key)) return -1;
Node node = map.get(key);
remove(node);
addToHead(node);
return node.value;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
Node node = map.get(key);
node.value = value;
remove(node);
addToHead(node);
} else {
if (map.size() >= capacity) {
Node toRemove = tail.prev;
remove(toRemove);
map.remove(toRemove.key);
}
Node node = new Node(key, value);
map.put(key, node);
addToHead(node);
}
}
private void remove(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void addToHead(Node node) {
node.next = head.next;
node.prev = head;
head.next.prev = node;
head.next = node;
}
}
3. 栈(Stack)
- LIFO(后进先出)
- 应用:表达式求值、括号匹配、单调栈
- 经典问题:
- 有效的括号
- 最小栈
- 每日温度
- 接雨水
// 典型题:有效的括号
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
Map<Character, Character> map = new HashMap<>();
map.put(')', '(');
map.put(']', '[');
map.put('}', '{');
for (char c : s.toCharArray()) {
if (map.containsKey(c)) {
if (stack.isEmpty() || stack.pop() != map.get(c)) {
return false;
}
} else {
stack.push(c);
}
}
return stack.isEmpty();
}
// 最小栈
class MinStack {
private Stack<Integer> stack = new Stack<>();
private Stack<Integer> minStack = new Stack<>();
public void push(int val) {
stack.push(val);
if (minStack.isEmpty() || val <= minStack.peek()) {
minStack.push(val);
}
}
public void pop() {
int val = stack.pop();
if (val == minStack.peek()) {
minStack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
4. 队列(Queue)
- FIFO(先进先出)
- 双端队列(Deque)
- 优先队列(PriorityQueue)
- 经典问题:
- 滑动窗口最大值
- 用栈实现队列
- Top K 问题
// 典型题:滑动窗口最大值
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || k <= 0) return new int[0];
int n = nums.length;
int[] result = new int[n - k + 1];
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
// 移除窗口外的元素
while (!deque.isEmpty() && deque.peek() < i - k + 1) {
deque.poll();
}
// 移除比当前元素小的元素(维护递减队列)
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offer(i);
// 记录结果
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peek()];
}
}
return result;
}
5. 哈希表(HashMap)
- 快速查找:O(1)
- 应用:计数、去重、快速查找
- 经典问题:
- 两数之和
- 字母异位词分组
- 最长连续序列
// 典型题:字母异位词分组
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String str : strs) {
char[] chars = str.toCharArray();
Arrays.sort(chars);
String key = new String(chars);
map.putIfAbsent(key, new ArrayList<>());
map.get(key).add(str);
}
return new ArrayList<>(map.values());
}
每日练习计划(Week 1-2):
- Day 1-3:数组(10题)
- Day 4-6:链表(10题)
- Day 7-9:栈和队列(10题)
- Day 10-12:哈希表(10题)
- Day 13-14:综合复习
阶段二:基础算法(Week 3-4)
目标: 掌握基础算法思想和实现
1. 排序算法
// 快速排序(重要!)
public void quickSort(int[] arr, int left, int right) {
if (left >= right) return;
int pivot = partition(arr, left, right);
quickSort(arr, left, pivot - 1);
quickSort(arr, pivot + 1, right);
}
private int partition(int[] arr, int left, int right) {
int pivot = arr[right];
int i = left - 1;
for (int j = left; j < right; j++) {
if (arr[j] < pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, right);
return i + 1;
}
// 归并排序
public void mergeSort(int[] arr, int left, int right) {
if (left >= right) return;
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
private void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
System.arraycopy(temp, 0, arr, left, temp.length);
}
排序算法对比:
| 算法 | 平均时间 | 最坏时间 | 空间 | 稳定性 | 适用场景 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 | 教学用,不实用 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 | 数据量小 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 | 部分有序数据 |
| 快速排序 | O(nlogn) | O(n²) | O(logn) | 不稳定 | 通用(最常用) |
| 归并排序 | O(nlogn) | O(nlogn) | O(n) | 稳定 | 需要稳定性 |
| 堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定 | Top K问题 |
2. 二分查找(Binary Search)⭐⭐⭐
// 标准二分查找
public int binarySearch(int[] nums, int target) {
int left = 0, 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;
}
// 查找左边界
public int searchLeft(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return left;
}
// 查找右边界
public int searchRight(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return right;
}
二分查找的关键点:
- 循环条件:
left <= right还是left < right - 边界更新:
mid + 1还是mid - 返回值:
left还是right
典型应用:
- 在旋转数组中查找
- 寻找峰值
- 搜索插入位置
- 平方根(二分答案)
3. 双指针(Two Pointers)⭐⭐⭐
// 对撞指针:两数之和 II
public int[] twoSum(int[] numbers, int target) {
int left = 0, right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum == target) {
return new int[] {left + 1, right + 1};
} else if (sum < target) {
left++;
} else {
right--;
}
}
return new int[] {};
}
// 快慢指针:链表中点
public ListNode findMiddle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
// 快慢指针:环形链表
public boolean hasCycle(ListNode head) {
if (head == null) return false;
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
// 滑动窗口:最长无重复子串
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int maxLen = 0, left = 0;
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
if (map.containsKey(c)) {
left = Math.max(left, map.get(c) + 1);
}
map.put(c, right);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
双指针技巧总结:
- 对撞指针:从两端向中间移动(两数之和、盛水最多的容器)
- 快慢指针:速度不同的指针(链表中点、环检测)
- 滑动窗口:左右指针维护窗口(子串问题、子数组问题)
阶段三:进阶数据结构(Week 5-6)
1. 树(Tree)⭐⭐⭐⭐⭐
二叉树遍历(必须熟练):
// 前序遍历(递归)
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
preorder(root, result);
return result;
}
private void preorder(TreeNode node, List<Integer> result) {
if (node == null) return;
result.add(node.val);
preorder(node.left, result);
preorder(node.right, result);
}
// 前序遍历(迭代)
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
}
return result;
}
// 中序遍历(迭代)
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
result.add(curr.val);
curr = curr.right;
}
return result;
}
// 层序遍历
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
List<Integer> level = new ArrayList<>();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
result.add(level);
}
return result;
}
二叉搜索树(BST):
// BST查找
public TreeNode searchBST(TreeNode root, int val) {
if (root == null || root.val == val) return root;
return val < root.val ? searchBST(root.left, val) : searchBST(root.right, val);
}
// BST插入
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) return new TreeNode(val);
if (val < root.val) {
root.left = insertIntoBST(root.left, val);
} else {
root.right = insertIntoBST(root.right, val);
}
return root;
}
// BST验证
public boolean isValidBST(TreeNode root) {
return validate(root, null, null);
}
private boolean validate(TreeNode node, Integer min, Integer max) {
if (node == null) return true;
if ((min != null && node.val <= min) || (max != null && node.val >= max)) {
return false;
}
return validate(node.left, min, node.val) && validate(node.right, node.val, max);
}
常见树的问题:
- 树的最大深度 / 最小深度
- 对称二叉树
- 路径总和
- 二叉树的最近公共祖先
- 二叉树的序列化与反序列化
2. 堆(Heap)
// Top K 问题(最小堆)
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : nums) {
minHeap.offer(num);
if (minHeap.size() > k) {
minHeap.poll();
}
}
return minHeap.peek();
}
// 数据流中的中位数
class MedianFinder {
private PriorityQueue<Integer> maxHeap; // 左半部分(大顶堆)
private PriorityQueue<Integer> minHeap; // 右半部分(小顶堆)
public MedianFinder() {
maxHeap = new PriorityQueue<>((a, b) -> b - a);
minHeap = new PriorityQueue<>();
}
public void addNum(int num) {
if (maxHeap.isEmpty() || num <= maxHeap.peek()) {
maxHeap.offer(num);
} else {
minHeap.offer(num);
}
// 平衡两个堆
if (maxHeap.size() > minHeap.size() + 1) {
minHeap.offer(maxHeap.poll());
} else if (minHeap.size() > maxHeap.size()) {
maxHeap.offer(minHeap.poll());
}
}
public double findMedian() {
if (maxHeap.size() > minHeap.size()) {
return maxHeap.peek();
}
return (maxHeap.peek() + minHeap.peek()) / 2.0;
}
}
3. 前缀树(Trie)
class Trie {
class TrieNode {
TrieNode[] children = new TrieNode[26];
boolean isEnd = false;
}
private TrieNode root;
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) {
node.children[index] = new TrieNode();
}
node = node.children[index];
}
node.isEnd = true;
}
public boolean search(String word) {
TrieNode node = searchPrefix(word);
return node != null && node.isEnd;
}
public boolean startsWith(String prefix) {
return searchPrefix(prefix) != null;
}
private TrieNode searchPrefix(String prefix) {
TrieNode node = root;
for (char c : prefix.toCharArray()) {
int index = c - 'a';
if (node.children[index] == null) {
return null;
}
node = node.children[index];
}
return node;
}
}
4. 并查集(Union-Find)
class UnionFind {
private int[] parent;
private int[] rank;
public UnionFind(int n) {
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
rank[i] = 1;
}
}
// 查找(路径压缩)
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
// 合并(按秩合并)
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY) return;
if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
// 是否连通
public boolean connected(int x, int y) {
return find(x) == find(y);
}
}
// 典型应用:朋友圈数量
public int findCircleNum(int[][] isConnected) {
int n = isConnected.length;
UnionFind uf = new UnionFind(n);
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (isConnected[i][j] == 1) {
uf.union(i, j);
}
}
}
Set<Integer> roots = new HashSet<>();
for (int i = 0; i < n; i++) {
roots.add(uf.find(i));
}
return roots.size();
}
阶段四:高级算法(Week 7-8)
1. 动态规划(DP)⭐⭐⭐⭐⭐
DP解题步骤:
- 定义状态(dp数组的含义)
- 找状态转移方程
- 确定初始状态
- 确定遍历顺序
- 优化空间复杂度(可选)
经典DP问题:
// 1. 爬楼梯(入门)
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 climbStairs(int n) {
if (n <= 2) return n;
int prev2 = 1, prev1 = 2;
for (int i = 3; i <= n; i++) {
int curr = prev1 + prev2;
prev2 = prev1;
prev1 = curr;
}
return prev1;
}
// 2. 打家劫舍
public int rob(int[] nums) {
if (nums.length == 0) return 0;
if (nums.length == 1) return nums[0];
int[] dp = new int[nums.length];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < nums.length; i++) {
dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]);
}
return dp[nums.length - 1];
}
// 3. 最长递增子序列
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
int maxLen = 1;
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
maxLen = Math.max(maxLen, dp[i]);
}
return maxLen;
}
// 4. 零钱兑换
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (i >= coin) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
// 5. 编辑距离(困难)
public int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[][] dp = new int[m + 1][n + 1];
// 初始化
for (int i = 0; i <= m; i++) dp[i][0] = i;
for (int j = 0; j <= n; j++) dp[0][j] = j;
// 状态转移
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (word1.charAt(i-1) == word2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = Math.min(
Math.min(dp[i-1][j], dp[i][j-1]),
dp[i-1][j-1]
) + 1;
}
}
}
return dp[m][n];
}
// 6. 最长公共子序列
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1.charAt(i-1) == text2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[m][n];
}
// 7. 股票买卖(最经典)
public int maxProfit(int[] prices) {
if (prices.length == 0) return 0;
int minPrice = prices[0];
int maxProfit = 0;
for (int i = 1; i < prices.length; i++) {
maxProfit = Math.max(maxProfit, prices[i] - minPrice);
minPrice = Math.min(minPrice, prices[i]);
}
return maxProfit;
}
DP题型分类:
- 线性DP:爬楼梯、打家劫舍、最长递增子序列
- 背包DP:0-1背包、完全背包、零钱兑换
- 区间DP:最长回文子串、戳气球
- 状态机DP:股票买卖
- 数位DP:数字计数
2. 贪心算法(Greedy)
// 1. 买卖股票的最佳时机 II
public int maxProfit(int[] prices) {
int profit = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] > prices[i-1]) {
profit += prices[i] - prices[i-1];
}
}
return profit;
}
// 2. 跳跃游戏
public boolean canJump(int[] nums) {
int maxReach = 0;
for (int i = 0; i < nums.length; i++) {
if (i > maxReach) return false;
maxReach = Math.max(maxReach, i + nums[i]);
}
return true;
}
// 3. 分发饼干
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int child = 0, cookie = 0;
while (child < g.length && cookie < s.length) {
if (s[cookie] >= g[child]) {
child++;
}
cookie++;
}
return child;
}
3. 回溯算法(Backtracking)
// 1. 全排列
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(nums, new ArrayList<>(), new boolean[nums.length], result);
return result;
}
private void backtrack(int[] nums, List<Integer> path, boolean[] used, List<List<Integer>> result) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) continue;
path.add(nums[i]);
used[i] = true;
backtrack(nums, path, used, result);
path.remove(path.size() - 1);
used[i] = false;
}
}
// 2. 子集
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrack(nums, 0, new ArrayList<>(), result);
return result;
}
private void backtrack(int[] nums, int start, List<Integer> path, List<List<Integer>> result) {
result.add(new ArrayList<>(path));
for (int i = start; i < nums.length; i++) {
path.add(nums[i]);
backtrack(nums, i + 1, path, result);
path.remove(path.size() - 1);
}
}
// 3. N皇后
public List<List<String>> solveNQueens(int n) {
List<List<String>> result = new ArrayList<>();
char[][] board = new char[n][n];
for (int i = 0; i < n; i++) {
Arrays.fill(board[i], '.');
}
backtrack(board, 0, result);
return result;
}
private void backtrack(char[][] board, int row, List<List<String>> result) {
if (row == board.length) {
result.add(construct(board));
return;
}
for (int col = 0; col < board.length; col++) {
if (!isValid(board, row, col)) continue;
board[row][col] = 'Q';
backtrack(board, row + 1, result);
board[row][col] = '.';
}
}
private boolean isValid(char[][] board, int row, int col) {
int n = board.length;
// 检查列
for (int i = 0; i < row; i++) {
if (board[i][col] == 'Q') return false;
}
// 检查左上对角线
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q') return false;
}
// 检查右上对角线
for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
if (board[i][j] == 'Q') return false;
}
return true;
}
private List<String> construct(char[][] board) {
List<String> result = new ArrayList<>();
for (char[] row : board) {
result.add(new String(row));
}
return result;
}
4. 图算法
// BFS
public int shortestPath(int[][] grid) {
int m = grid.length, n = grid[0].length;
Queue<int[]> queue = new LinkedList<>();
boolean[][] visited = new boolean[m][n];
queue.offer(new int[]{0, 0, 0}); // row, col, dist
visited[0][0] = true;
int[][] dirs = {{0,1}, {1,0}, {0,-1}, {-1,0}};
while (!queue.isEmpty()) {
int[] curr = queue.poll();
int row = curr[0], col = curr[1], dist = curr[2];
if (row == m-1 && col == n-1) {
return dist;
}
for (int[] dir : dirs) {
int newRow = row + dir[0];
int newCol = col + dir[1];
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n
&& !visited[newRow][newCol] && grid[newRow][newCol] == 0) {
queue.offer(new int[]{newRow, newCol, dist + 1});
visited[newRow][newCol] = true;
}
}
}
return -1;
}
// DFS
public void dfs(int[][] grid, int row, int col, boolean[][] visited) {
if (row < 0 || row >= grid.length || col < 0 || col >= grid[0].length
|| visited[row][col] || grid[row][col] == 1) {
return;
}
visited[row][col] = true;
dfs(grid, row + 1, col, visited);
dfs(grid, row - 1, col, visited);
dfs(grid, row, col + 1, visited);
dfs(grid, row, col - 1, visited);
}
// 拓扑排序
public int[] topologicalSort(int numCourses, int[][] prerequisites) {
List<List<Integer>> graph = new ArrayList<>();
int[] inDegree = new int[numCourses];
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
for (int[] edge : prerequisites) {
graph.get(edge[1]).add(edge[0]);
inDegree[edge[0]]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) {
queue.offer(i);
}
}
int[] result = new int[numCourses];
int index = 0;
while (!queue.isEmpty()) {
int curr = queue.poll();
result[index++] = curr;
for (int next : graph.get(curr)) {
inDegree[next]--;
if (inDegree[next] == 0) {
queue.offer(next);
}
}
}
return index == numCourses ? result : new int[0];
}
四、刷题策略与技巧
4.1 刷题原则
1. 质量 > 数量
- 一道题搞透,胜过刷十道题
- 理解多种解法,总结规律
- 注重复习,防止遗忘
2. 循序渐进
- 从简单到困难
- 从基础到进阶
- 从单一知识点到综合应用
3. 主动思考
- 先自己思考15-30分钟
- 实在没思路再看题解
- 看完题解后自己重新实现
4. 举一反三
- 总结同类型题目的解法
- 思考变形题如何解决
- 归纳通用模板
4.2 刷题计划
初级阶段(0-150题):
- 目标:熟悉基础数据结构和算法
- 策略:按专题刷题,每个专题10-20题
- 时间:每天2-3题,持续2个月
中级阶段(150-300题):
- 目标:掌握常见套路和技巧
- 策略:混合刷题,注重总结
- 时间:每天1-2题,持续3个月
高级阶段(300+题):
- 目标:快速解题,追求最优解
- 策略:按公司标签刷,模拟面试
- 时间:每天1题+复习,持续进行
4.3 LeetCode刷题路线
精选题单(按优先级):
高频Easy(必刷50题):
- 两数之和
- 反转链表
- 有效的括号
- 合并两个有序链表
- 二叉树的最大深度
- 对称二叉树
- 爬楼梯
- 最大子数组和
- 买卖股票的最佳时机
- …
高频Medium(必刷100题):
- 三数之和
- 无重复字符的最长子串
- 盛最多水的容器
- 环形链表 II
- LRU缓存
- 二叉树的层序遍历
- 岛屿数量
- 最长递增子序列
- 零钱兑换
- 全排列
- …
高频Hard(选刷30题):
- 接雨水
- 合并K个升序链表
- 正则表达式匹配
- 编辑距离
- 最小覆盖子串
- N皇后
- …
4.4 刷题技巧
1. 识别题型
题目特征 → 可能的解法
───────────────────────────
数组/字符串 + 子数组/子串 → 滑动窗口、双指针
有序数组 + 查找 → 二分查找
树的遍历/路径 → DFS/BFS
岛屿/连通性 → DFS/BFS/并查集
最优解/计数 → 动态规划
排列组合/枚举 → 回溯
Top K → 堆
前缀匹配 → Trie
2. 时间复杂度估算
数据量 → 可接受的复杂度 → 可能的算法
──────────────────────────────────
n ≤ 10 → O(n!) → 全排列、暴力搜索
n ≤ 20 → O(2^n) → 状态压缩DP、回溯
n ≤ 100 → O(n^3) → 三重循环
n ≤ 1000 → O(n^2) → 双重循环、DP
n ≤ 10^5 → O(nlogn) → 排序、堆、分治
n ≤ 10^6 → O(n) → 哈希、双指针
n ≤ 10^9 → O(logn) → 二分查找
3. 解题模板
二分查找模板:
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (check(mid)) {
// 记录答案
left = mid + 1; // 或 right = mid - 1
} else {
right = mid - 1; // 或 left = mid + 1
}
}
回溯模板:
void backtrack(参数) {
if (终止条件) {
收集结果;
return;
}
for (选择 in 选择列表) {
做选择;
backtrack(参数);
撤销选择;
}
}
DP模板:
// 1. 定义dp数组
int[] dp = new int[n];
// 2. 初始化
dp[0] = ...;
// 3. 状态转移
for (int i = 1; i < n; i++) {
dp[i] = f(dp[i-1], dp[i-2], ...);
}
// 4. 返回结果
return dp[n-1];
4. 调试技巧
// 打印调试信息
System.out.println("当前状态: " + Arrays.toString(arr));
System.out.println("i=" + i + ", j=" + j);
// 断点调试
// 在IDE中设置断点,单步执行
// 小数据测试
// 用简单的输入验证逻辑正确性
五、高频面试算法题
5.1 大厂高频TOP50
字节跳动 TOP 10:
- 三数之和
- 合并区间
- LRU缓存
- 岛屿数量
- 最长递增子序列
- 接雨水
- 全排列
- 二叉树的最近公共祖先
- 最小覆盖子串
- 股票买卖系列
阿里巴巴 TOP 10:
- 两数之和
- 反转链表
- 有效的括号
- 二叉树的层序遍历
- 最大子数组和
- 爬楼梯
- 环形链表
- 合并两个有序链表
- 删除链表的倒数第N个节点
- 二叉树的最大深度
腾讯 TOP 10:
- 无重复字符的最长子串
- 盛最多水的容器
- 搜索旋转排序数组
- 字符串相加
- 二叉树的右视图
- 岛屿数量
- 零钱兑换
- 最长公共子序列
- 螺旋矩阵
- 排序链表
5.2 按公司刷题策略
准备面试流程:
- 查看目标公司的高频题单(LeetCode企业标签)
- 优先刷频率最高的50题
- 做2-3套模拟面试
- 总结常见考点和自己的弱项
- 针对性强化训练
时间分配(假设2个月准备):
- Week 1-4:基础巩固(150题)
- Week 5-6:公司高频题(50题)
- Week 7:专题突破(弱项加强)
- Week 8:模拟面试+复习
六、学习资源推荐
6.1 在线平台
刷题平台:
-
LeetCode - 最推荐,题库最全
- 中文站:leetcode.cn
- 按难度、标签、公司筛选
- 查看题解和讨论
-
牛客网 - 国内企业真题
- 各大公司笔试真题
- 在线编程练习
- 面经分享
-
Codeforces - 竞赛级别
- 适合算法竞赛选手
- 难度较高
6.2 书籍推荐
入门级:
-
《算法图解》- Aditya Bhargava
- 图文并茂,适合零基础
- 轻松理解核心算法
-
《啊哈!算法》- 啊哈磊
- 趣味性强,适合初学者
进阶级:
3. 《剑指Offer》 - 何海涛(强烈推荐)
- 面试必备
- 题目经典,讲解详细
-
《程序员代码面试指南》 - 左程云
- 题目全面
- Java实现
-
《算法4》- Robert Sedgewick
- 经典教材
- Java实现
竞赛级:
6. 《算法竞赛进阶指南》- 李煜东
7. 《挑战程序设计竞赛》
6.3 视频课程
-
李煜东《算法竞赛进阶指南》 - B站
-
代码随想录 - B站/公众号(强烈推荐)
- LeetCode题解
- 系统学习路线
- 动画演示
-
花花酱 LeetCode - YouTube/B站
- 视频题解
- 讲解清晰
-
左程云《算法课》 - B站
- 系统讲解
- Java实现
6.4 学习社区
-
LeetCode中国站讨论区
- 查看题解
- 参与讨论
-
代码随想录网站
- 系统学习路线
- 题目分类整理
-
GitHub awesome-algorithms
- 算法资源汇总
-
知乎算法话题
- 学习经验分享
- 面试经验
七、Java开发者特别建议
7.1 Java语言特性在算法中的应用
1. 集合框架
// ArrayList - 动态数组
List<Integer> list = new ArrayList<>();
list.add(1);
list.get(0);
list.remove(0);
// LinkedList - 链表(可当栈和队列用)
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.addFirst(1); // 头插
linkedList.addLast(2); // 尾插
linkedList.removeFirst(); // 出队
linkedList.removeLast(); // 出栈
// HashMap - 哈希表
Map<String, Integer> map = new HashMap<>();
map.put("key", 1);
map.getOrDefault("key", 0);
map.putIfAbsent("key", 1);
// TreeMap - 有序Map(红黑树)
TreeMap<Integer, Integer> treeMap = new TreeMap<>();
treeMap.firstKey(); // 最小键
treeMap.lastKey(); // 最大键
treeMap.ceilingKey(5); // >= 5的最小键
treeMap.floorKey(5); // <= 5的最大键
// TreeSet - 有序Set
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(1);
treeSet.ceiling(5); // >= 5的最小值
treeSet.floor(5); // <= 5的最大值
// PriorityQueue - 优先队列(堆)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
// Stack - 栈
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.pop();
stack.peek();
// Queue - 队列
Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.poll();
queue.peek();
2. 常用API
// 数组操作
Arrays.sort(arr); // 排序
Arrays.fill(arr, 0); // 填充
Arrays.copyOf(arr, len); // 复制
Arrays.toString(arr); // 转字符串
// 字符串操作
String s = "hello";
s.length();
s.charAt(0);
s.substring(0, 2);
s.toCharArray(); // 转字符数组
String.valueOf(arr); // 字符数组转字符串
// 集合操作
Collections.sort(list);
Collections.reverse(list);
Collections.max(list);
Collections.min(list);
// 数学函数
Math.max(a, b);
Math.min(a, b);
Math.abs(a);
Math.pow(2, 3);
Math.sqrt(4);
3. Lambda表达式
// 自定义比较器
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
// PriorityQueue自定义
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
// 流式操作
List<Integer> result = list.stream()
.filter(x -> x > 0)
.map(x -> x * 2)
.collect(Collectors.toList());
7.2 Java vs C++ 在算法题中的对比
| 特性 | Java | C++ |
|---|---|---|
| 执行速度 | 较慢 | 快 |
| 内存占用 | 较大 | 小 |
| 语法复杂度 | 简单 | 复杂 |
| STL vs 集合框架 | 集合框架易用 | STL更强大 |
| 数组操作 | 简单 | 灵活 |
| 适合场景 | 在线笔试、面试 | 算法竞赛 |
建议:
- 面试和笔试用Java(语法简单,不容易出错)
- 算法竞赛用C++(速度快)
- 工作中根据项目语言选择
7.3 常见陷阱
1. 整数溢出
// 错误
int mid = (left + right) / 2; // 可能溢出
// 正确
int mid = left + (right - left) / 2;
2. 数组越界
// 检查边界
if (i >= 0 && i < arr.length) {
// 访问arr[i]
}
3. 空指针
// 检查null
if (node != null) {
// 访问node
}
4. 比较器陷阱
// 错误:可能溢出
Arrays.sort(arr, (a, b) -> a - b);
// 正确
Arrays.sort(arr, (a, b) -> Integer.compare(a, b));
7.4 效率提升技巧
1. 使用基本类型
// 慢:装箱拆箱开销
List<Integer> list = new ArrayList<>();
// 快:直接使用数组
int[] arr = new int[n];
2. 预分配空间
// 慢
List<Integer> list = new ArrayList<>();
// 快
List<Integer> list = new ArrayList<>(1000);
3. StringBuilder vs String
// 慢:每次拼接创建新对象
String s = "";
for (int i = 0; i < n; i++) {
s += i;
}
// 快
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append(i);
}
String s = sb.toString();
7.5 刷题环境配置
IntelliJ IDEA配置:
// 创建LeetCode项目
// 使用JUnit进行测试
import org.junit.Test;
import static org.junit.Assert.*;
public class Solution {
public int twoSum(int[] nums, int target) {
// 实现
}
@Test
public void test1() {
int[] nums = {2, 7, 11, 15};
int target = 9;
assertEquals(2, twoSum(nums, target));
}
}
LeetCode插件:
- 安装:Settings → Plugins → LeetCode
- 功能:在IDE中直接刷题、提交
- 优势:不用切换浏览器,调试方便
八、持续进步的建议
8.1 每日计划
工作日(1-2小时):
- 20:00-20:30:复习昨天的题目
- 20:30-21:30:刷1-2道新题
- 21:30-22:00:总结归纳
周末(3-4小时):
- 上午:专题突破(10-15题)
- 下午:模拟面试(5题,限时90分钟)
- 晚上:总结本周内容
8.2 学习误区
❌ 错误做法:
- 只刷题不总结
- 看答案不自己实现
- 追求数量不求质量
- 长时间不复习
- 畏难情绪,跳过困难题
✅ 正确做法:
- 每道题总结多种解法
- 自己独立实现代码
- 一道题做到完全理解
- 定期复习重点题目
- 循序渐进,不急于求成
8.3 进阶方向
1. 算法竞赛
- 参加Codeforces、AtCoder
- 提升编程速度和正确率
- 学习高级算法(网络流、计算几何)
2. 系统设计
- 学习分布式系统
- 缓存、消息队列
- 高并发、高可用
3. 领域专精
- 推荐算法
- 搜索算法
- 图像算法
- NLP算法
8.4 面试准备
面试前1周:
- 复习高频题(50题)
- 模拟面试(3-5次)
- 准备自我介绍和项目介绍
- 整理常见问题答案
面试中:
- 先理解题意,确认输入输出
- 询问数据范围,确定复杂度
- 说出思路,得到确认再写代码
- 边写边说,解释关键步骤
- 写完后自己检查边界情况
- 分析时间空间复杂度
面试后:
- 记录面试题目
- 回顾不会的题
- 总结面试经验
- 继续刷题
九、总结
9.1 核心要点
- 基础扎实:熟练掌握数据结构和基础算法
- 大量练习:至少刷300道题才能应对面试
- 主动思考:理解原理,不是背答案
- 定期复习:防止遗忘,巩固记忆
- 归纳总结:整理模板,举一反三
9.2 刷题里程碑
- ✅ 50题:入门,了解基本题型
- ✅ 150题:掌握基础,能解简单题
- ✅ 300题:应对大厂面试
- ✅ 500题:解题熟练,追求最优解
- ✅ 800题:算法专家
9.3 心态调整
遇到困难时:
- 算法是可以通过练习提升的技能
- 大部分人都经历过从不会到会的过程
- 每个人都有自己的节奏,不要和别人比
- 坚持下去,量变会产生质变
保持动力:
- 设定小目标(每周10题)
- 记录进步(刷题数、错题本)
- 加入学习小组(互相监督)
- 想象拿到offer的喜悦
附录:常用代码模板
A. 二分查找模板
// 标准二分
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
// 查找左边界
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
return left;
// 查找右边界
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) left = mid + 1;
else right = mid - 1;
}
return right;
B. 回溯模板
void backtrack(路径, 选择列表) {
if (满足结束条件) {
result.add(路径);
return;
}
for (选择 in 选择列表) {
做选择;
backtrack(路径, 选择列表);
撤销选择;
}
}
C. 树的遍历模板
// DFS
void dfs(TreeNode root) {
if (root == null) return;
// 前序位置
dfs(root.left);
// 中序位置
dfs(root.right);
// 后序位置
}
// BFS
void bfs(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
// 处理节点
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
}
D. DP模板
// 一维DP
int[] dp = new int[n + 1];
dp[0] = 初始值;
for (int i = 1; i <= n; i++) {
dp[i] = 状态转移;
}
// 二维DP
int[][] dp = new int[m + 1][n + 1];
// 初始化
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = 状态转移;
}
}
最后的话:
算法能力的提升是一个循序渐进的过程,没有捷径,只有多练。保持耐心,持之以恒,你一定能达到目标!
祝你刷题顺利,面试成功!🚀
参考资料:
- LeetCode官方题库
- 《剑指Offer》
- 《程序员代码面试指南》
- 代码随想录
- 个人刷题经验总结
算法能力提升全攻略
13万+

被折叠的 条评论
为什么被折叠?



