提升算法能力就这样办

算法能力提升全攻略

算法能力提升

目录


一、为什么要提高算法能力

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解题步骤:

  1. 定义状态(dp数组的含义)
  2. 找状态转移方程
  3. 确定初始状态
  4. 确定遍历顺序
  5. 优化空间复杂度(可选)

经典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:

  1. 三数之和
  2. 合并区间
  3. LRU缓存
  4. 岛屿数量
  5. 最长递增子序列
  6. 接雨水
  7. 全排列
  8. 二叉树的最近公共祖先
  9. 最小覆盖子串
  10. 股票买卖系列

阿里巴巴 TOP 10:

  1. 两数之和
  2. 反转链表
  3. 有效的括号
  4. 二叉树的层序遍历
  5. 最大子数组和
  6. 爬楼梯
  7. 环形链表
  8. 合并两个有序链表
  9. 删除链表的倒数第N个节点
  10. 二叉树的最大深度

腾讯 TOP 10:

  1. 无重复字符的最长子串
  2. 盛最多水的容器
  3. 搜索旋转排序数组
  4. 字符串相加
  5. 二叉树的右视图
  6. 岛屿数量
  7. 零钱兑换
  8. 最长公共子序列
  9. 螺旋矩阵
  10. 排序链表

5.2 按公司刷题策略

准备面试流程:

  1. 查看目标公司的高频题单(LeetCode企业标签)
  2. 优先刷频率最高的50题
  3. 做2-3套模拟面试
  4. 总结常见考点和自己的弱项
  5. 针对性强化训练

时间分配(假设2个月准备):

  • Week 1-4:基础巩固(150题)
  • Week 5-6:公司高频题(50题)
  • Week 7:专题突破(弱项加强)
  • Week 8:模拟面试+复习

六、学习资源推荐

6.1 在线平台

刷题平台:

  1. LeetCode - 最推荐,题库最全

    • 中文站:leetcode.cn
    • 按难度、标签、公司筛选
    • 查看题解和讨论
  2. 牛客网 - 国内企业真题

    • 各大公司笔试真题
    • 在线编程练习
    • 面经分享
  3. Codeforces - 竞赛级别

    • 适合算法竞赛选手
    • 难度较高

6.2 书籍推荐

入门级:

  1. 《算法图解》- Aditya Bhargava

    • 图文并茂,适合零基础
    • 轻松理解核心算法
  2. 《啊哈!算法》- 啊哈磊

    • 趣味性强,适合初学者

进阶级:
3. 《剑指Offer》 - 何海涛(强烈推荐)

  • 面试必备
  • 题目经典,讲解详细
  1. 《程序员代码面试指南》 - 左程云

    • 题目全面
    • Java实现
  2. 《算法4》- Robert Sedgewick

    • 经典教材
    • Java实现

竞赛级:
6. 《算法竞赛进阶指南》- 李煜东
7. 《挑战程序设计竞赛》

6.3 视频课程

  1. 李煜东《算法竞赛进阶指南》 - B站

  2. 代码随想录 - B站/公众号(强烈推荐)

    • LeetCode题解
    • 系统学习路线
    • 动画演示
  3. 花花酱 LeetCode - YouTube/B站

    • 视频题解
    • 讲解清晰
  4. 左程云《算法课》 - B站

    • 系统讲解
    • Java实现

6.4 学习社区

  1. LeetCode中国站讨论区

    • 查看题解
    • 参与讨论
  2. 代码随想录网站

    • 系统学习路线
    • 题目分类整理
  3. GitHub awesome-algorithms

    • 算法资源汇总
  4. 知乎算法话题

    • 学习经验分享
    • 面试经验

七、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++ 在算法题中的对比

特性JavaC++
执行速度较慢
内存占用较大
语法复杂度简单复杂
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 学习误区

❌ 错误做法:

  1. 只刷题不总结
  2. 看答案不自己实现
  3. 追求数量不求质量
  4. 长时间不复习
  5. 畏难情绪,跳过困难题

✅ 正确做法:

  1. 每道题总结多种解法
  2. 自己独立实现代码
  3. 一道题做到完全理解
  4. 定期复习重点题目
  5. 循序渐进,不急于求成

8.3 进阶方向

1. 算法竞赛

  • 参加Codeforces、AtCoder
  • 提升编程速度和正确率
  • 学习高级算法(网络流、计算几何)

2. 系统设计

  • 学习分布式系统
  • 缓存、消息队列
  • 高并发、高可用

3. 领域专精

  • 推荐算法
  • 搜索算法
  • 图像算法
  • NLP算法

8.4 面试准备

面试前1周:

  • 复习高频题(50题)
  • 模拟面试(3-5次)
  • 准备自我介绍和项目介绍
  • 整理常见问题答案

面试中:

  • 先理解题意,确认输入输出
  • 询问数据范围,确定复杂度
  • 说出思路,得到确认再写代码
  • 边写边说,解释关键步骤
  • 写完后自己检查边界情况
  • 分析时间空间复杂度

面试后:

  • 记录面试题目
  • 回顾不会的题
  • 总结面试经验
  • 继续刷题

九、总结

9.1 核心要点

  1. 基础扎实:熟练掌握数据结构和基础算法
  2. 大量练习:至少刷300道题才能应对面试
  3. 主动思考:理解原理,不是背答案
  4. 定期复习:防止遗忘,巩固记忆
  5. 归纳总结:整理模板,举一反三

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》
  • 《程序员代码面试指南》
  • 代码随想录
  • 个人刷题经验总结
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值