【算法突围 03】核心算法思想:分治/递归/动态规划与 LeetCode 高频真题解析

【算法突围 03】核心算法思想:分治/递归/动态规划与 LeetCode 高频真题解析

📖 本文导读

为什么快速排序平均 O(N log N) 却最坏 O(N²)?递归如何避免栈溢出?动态规划真的那么难吗?LeetCode 刷题总是没有思路怎么办?

本文将通过分治法 (快速排序)、递归与回溯 (文件遍历、全排列)、动态规划 (爬楼梯、零钱兑换)三大核心思想,展示从暴力解法推导到最优解的完整思维过程。所有代码均用 Java 实现,逐行注释,配合 LeetCode 高频真题实战。

适合人群:算法初学者、准备 LeetCode 面试者、想提升算法思维的 Java 开发者。

阅读收获 :掌握分治/递归/动态规划的核心套路,学会识别 DP 问题,形成自己的解题模板,能够独立分析 LeetCode 中等难度题目。


一、引言:算法不是刷题,是解决问题的思维

误区:算法没用?

很多开发者工作后会产生一种错觉:"我天天写 CRUD,算法根本用不上。"

确实,如果你只是简单地从数据库查数据、组装 JSON、返回给前端,确实用不到什么高深算法。但当你遇到以下场景时,算法能力直接决定了系统的上限:

场景 算法能力决定什么
秒杀系统 库存扣减的并发控制,用 Redis 原子操作还是 Lua 脚本?
推荐系统 如何快速从百万商品中找到用户可能感兴趣的 Top 100?
日志分析 如何在亿级日志中快速定位异常?
路径规划 外卖骑手如何规划最短配送路线?

真相:算法决定了系统的上限

举个例子:秒杀系统的库存扣减

  • 初级做法:先查库存,再判断,再扣减。并发一上来,超卖!
  • 进阶做法 :用 Redis 的 DECR 原子操作,但 Redis 挂了怎么办?
  • 高手做法:Redis 预扣减 + 消息队列异步落库 + 兜底方案。

这里面的每一个决策,都需要你对数据结构的性能特征算法的复杂度有深刻理解。

本文的目标

本文不讲所有排序算法,也不罗列 LeetCode 题目。我们要做的是:

  1. 理解核心思想:分治、递归、动态规划。
  2. 从暴力到最优:展示思维推导过程,而不是直接给答案。
  3. Java 实战:所有代码都用 Java 实现,逐行注释。

准备好了吗?让我们开始这场算法思维的修炼之旅。


二、排序算法:快速排序的深度剖析

2.1 思想:分治法(Divide and Conquer)

快速排序(Quick Sort)的核心思想是分治法

将一个大问题分解为若干个小问题,分别解决,再合并结果。

类比:整理一副乱序的扑克牌。

  • 普通做法:一张张找最小的,放左边(选择排序,O(N²))。
  • 快排做法:随便抽一张牌作为"基准",比它小的放左边,大的放右边。然后递归整理左右两堆。

2.2 步骤:选基准、分区、递归

复制代码
原始数组:[3, 6, 8, 10, 1, 2, 1]

第 1 轮:选 3 作为基准
  小于 3:[1, 2, 1]
  等于 3:[3]
  大于 3:[6, 8, 10]

第 2 轮:递归处理 [1, 2, 1],选 1 作为基准
  小于 1:[]
  等于 1:[1, 1]
  大于 1:[2]

第 3 轮:递归处理 [6, 8, 10],选 6 作为基准
  ...

最终结果:[1, 1, 2, 3, 6, 8, 10]

2.3 代码实现:手写 QuickSort

java 复制代码
import java.util.Arrays;

/**
 * 快速排序实现
 * 时间复杂度:平均 O(N log N),最坏 O(N²)
 * 空间复杂度:O(log N) 递归栈空间
 */
public class QuickSort {

    /**
     * 对外暴露的排序方法
     * @param arr 待排序数组
     */
    public static void sort(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;  // 空数组或单元素,无需排序
        }
        quickSort(arr, 0, arr.length - 1);
    }

    /**
     * 递归实现快速排序
     * @param arr  数组
     * @param left  左边界索引
     * @param right 右边界索引
     */
    private static void quickSort(int[] arr, int left, int right) {
        // 终止条件:子数组只剩一个元素
        if (left >= right) {
            return;
        }

        // 分区操作:返回基准元素的最终位置
        int pivotIndex = partition(arr, left, right);

        // 递归排序左半部分(小于基准的元素)
        quickSort(arr, left, pivotIndex - 1);

        // 递归排序右半部分(大于基准的元素)
        quickSort(arr, pivotIndex + 1, right);
    }

    /**
     * 分区操作:将数组分为 <pivot, =pivot, >pivot 三部分
     * @return 基准元素的最终位置
     */
    private static int partition(int[] arr, int left, int right) {
        // 选择最右边的元素作为基准
        int pivot = arr[right];

        // i 指向"小于基准"区域的下一个位置
        int i = left;

        // j 遍历数组,将小于基准的元素放到左边
        for (int j = left; j < right; j++) {
            if (arr[j] < pivot) {
                // 发现小于基准的元素,交换到 i 位置
                swap(arr, i, j);
                i++;  // 扩展"小于区域"
            }
        }

        // 将基准元素放到正确位置(i 位置)
        swap(arr, i, right);

        return i;  // 返回基准元素的最终索引
    }

    /**
     * 交换数组中两个元素的位置
     */
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 测试
    public static void main(String[] args) {
        int[] arr = {3, 6, 8, 10, 1, 2, 1};
        System.out.println("排序前:" + Arrays.toString(arr));
        sort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
        // 输出:[1, 1, 2, 3, 6, 8, 10]
    }
}

2.4 优化:三路快排(处理大量重复元素)

当数组中有大量重复元素时,普通快排会退化到 O(N²)。三路快排 将数组分为三部分:< pivot== pivot> pivot

java 复制代码
/**
 * 三路快速排序
 * 适用于有大量重复元素的场景
 */
public class ThreeWayQuickSort {

    public static void sort(int[] arr) {
        if (arr == null || arr.length <= 1) return;
        quickSort3Way(arr, 0, arr.length - 1);
    }

    private static void quickSort3Way(int[] arr, int left, int right) {
        if (left >= right) return;

        // 随机选择基准,避免最坏情况
        int randomIndex = left + (int)(Math.random() * (right - left + 1));
        swap(arr, left, randomIndex);
        int pivot = arr[left];

        // 三路分区指针
        // [left, lt)     : < pivot
        // [lt, gt]       : == pivot
        // (gt, right]    : > pivot
        int lt = left;      // less than 的边界
        int gt = right;     // greater than 的边界
        int i = left + 1;   // 当前遍历位置

        while (i <= gt) {
            if (arr[i] < pivot) {
                // 小于基准,交换到 lt 区域
                swap(arr, i, lt);
                lt++;
                i++;
            } else if (arr[i] > pivot) {
                // 大于基准,交换到 gt 区域
                swap(arr, i, gt);
                gt--;
                // i 不递增,因为交换过来的元素还没处理
            } else {
                // 等于基准,直接跳过
                i++;
            }
        }

        // 递归处理小于和大于区域(等于区域已经有序)
        quickSort3Way(arr, left, lt - 1);
        quickSort3Way(arr, gt + 1, right);
    }

    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

2.5 实战:利用 PriorityQueue 解决 Top K 问题

问题:从 10 亿个数字中找到最大的 100 个。

思路:维护一个大小为 K 的小顶堆,遍历所有数字:

  • 如果堆不满,直接加入。
  • 如果堆已满,比较当前数字与堆顶,如果更大则替换。
java 复制代码
import java.util.PriorityQueue;
import java.util.Arrays;

/**
 * Top K 问题:找出数组中最大的 K 个元素
 */
public class TopK {

    /**
     * 使用小顶堆找最大的 K 个元素
     * 时间复杂度:O(N log K)
     * 空间复杂度:O(K)
     */
    public static int[] findTopK(int[] nums, int k) {
        if (k <= 0 || nums == null || nums.length == 0) {
            return new int[0];
        }

        // 小顶堆:堆顶是最小的元素
        PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);

        for (int num : nums) {
            if (minHeap.size() < k) {
                // 堆还没满,直接加入
                minHeap.offer(num);
            } else if (num > minHeap.peek()) {
                // 当前数字比堆顶大,替换堆顶
                minHeap.poll();
                minHeap.offer(num);
            }
        }

        // 将堆中元素转为数组
        int[] result = new int[minHeap.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = minHeap.poll();
        }
        return result;
    }

    public static void main(String[] args) {
        int[] nums = {3, 2, 1, 5, 6, 4, 8, 7, 9};
        int k = 3;
        int[] topK = findTopK(nums, k);
        System.out.println("最大的 " + k + " 个数:" + Arrays.toString(topK));
        // 输出:[7, 8, 9](顺序可能不同)
    }
}

三、递归与回溯:程序的"套娃"艺术

3.1 递归三要素

递归就像俄罗斯套娃,每个娃娃里面都装着一个更小的自己。写递归函数时,必须明确三个要素:

复制代码
1. 终止条件:什么时候停止递归?(最小的娃娃不能再拆了)
2. 递归逻辑:如何分解问题?(当前娃娃做什么,剩下的交给下一个)
3. 状态恢复:回溯时如何恢复原状?(把娃娃重新装好)

3.2 案例 1:文件树遍历

java 复制代码
import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * 递归遍历文件目录
 */
public class FileTreeTraversal {

    /**
     * 递归列出目录下所有文件
     * @param dirPath 目录路径
     * @return 所有文件的完整路径列表
     */
    public static List<String> listAllFiles(String dirPath) {
        List<String> result = new ArrayList<>();
        File dir = new File(dirPath);

        if (!dir.exists() || !dir.isDirectory()) {
            return result;  // 终止条件:不是有效目录
        }

        listFilesRecursive(dir, result);
        return result;
    }

    private static void listFilesRecursive(File dir, List<String> result) {
        File[] files = dir.listFiles();

        // 终止条件:空目录
        if (files == null) {
            return;
        }

        for (File file : files) {
            if (file.isFile()) {
                // 是文件,加入结果
                result.add(file.getAbsolutePath());
            } else if (file.isDirectory()) {
                // 是目录,递归遍历
                System.out.println("进入目录:" + file.getName());
                listFilesRecursive(file, result);
                System.out.println("退出目录:" + file.getName());
            }
        }
    }

    public static void main(String[] args) {
        List<String> files = listAllFiles(".");
        System.out.println("共找到 " + files.size() + " 个文件");
    }
}

3.3 案例 2:全排列问题(回溯法)

问题 :给定数组 [1, 2, 3],输出所有排列。

思路

  1. 选择一个数作为当前位的值。
  2. 递归处理剩下的数。
  3. 回溯:撤销选择,尝试其他可能。
java 复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * 全排列问题:回溯法经典案例
 */
public class Permutations {

    private List<List<Integer>> result = new ArrayList<>();

    /**
     * 获取数组的所有排列
     */
    public List<List<Integer>> permute(int[] nums) {
        List<Integer> path = new ArrayList<>();
        boolean[] used = new boolean[nums.length];  // 标记哪些数字已使用
        backtrack(nums, path, used);
        return result;
    }

    /**
     * 回溯函数
     * @param nums  原始数组
     * @param path  当前已选择的数字(路径)
     * @param used  标记数组,used[i] 表示 nums[i] 是否已使用
     */
    private void backtrack(int[] nums, List<Integer> path, boolean[] used) {
        // 终止条件:路径长度等于数组长度,说明找到一个完整排列
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path));  // 必须新建一个副本!
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            if (used[i]) {
                continue;  // 跳过已使用的数字
            }

            // 做选择:将 nums[i] 加入路径
            path.add(nums[i]);
            used[i] = true;

            // 递归:进入下一层决策树
            backtrack(nums, path, used);

            // 撤销选择:回溯,恢复状态
            path.remove(path.size() - 1);
            used[i] = false;
        }
    }

    public static void main(String[] args) {
        Permutations solution = new Permutations();
        int[] nums = {1, 2, 3};
        List<List<Integer>> result = solution.permute(nums);

        System.out.println("所有排列:");
        for (List<Integer> perm : result) {
            System.out.println(perm);
        }
        // 输出:
        // [1, 2, 3]
        // [1, 3, 2]
        // [2, 1, 3]
        // [2, 3, 1]
        // [3, 1, 2]
        // [3, 2, 1]
    }
}

3.4 陷阱:栈溢出(StackOverflowError)如何避免?

递归的致命缺陷:每次递归调用都会消耗栈空间

复制代码
递归深度 10000 时:
- 每个栈帧约占用 1KB
- 总栈空间:10000 * 1KB = 10MB
- 超过 JVM 默认栈大小,抛出 StackOverflowError

解决方案

方法 说明
尾递归优化 某些编译器/JVM 可以优化尾递归(Java 目前不支持)
显式栈 用 Stack 或 Deque 手动模拟递归过程
限制递归深度 设置最大递归深度,超过则改用迭代

用显式栈改写文件遍历

java 复制代码
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * 使用显式栈避免递归深度过大
 */
public class FileTraversalIterative {

    public static List<String> listAllFiles(String dirPath) {
        List<String> result = new ArrayList<>();
        File rootDir = new File(dirPath);

        if (!rootDir.exists() || !rootDir.isDirectory()) {
            return result;
        }

        // 用栈代替递归
        Stack<File> stack = new Stack<>();
        stack.push(rootDir);

        while (!stack.isEmpty()) {
            File current = stack.pop();
            File[] files = current.listFiles();

            if (files == null) continue;

            for (File file : files) {
                if (file.isFile()) {
                    result.add(file.getAbsolutePath());
                } else if (file.isDirectory()) {
                    stack.push(file);  // 将子目录压栈
                }
            }
        }

        return result;
    }
}

四、动态规划(DP):从"爬楼梯"看最优子结构

4.1 核心思想:记住过去,避免重复

动态规划(Dynamic Programming)的本质是用空间换时间

将大问题分解为重叠的子问题,存储子问题的解,避免重复计算。

类比:走楼梯。

假设你要爬 10 级楼梯,每次可以走 1 级或 2 级。有多少种走法?

  • 暴力递归:f(10) = f(9) + f(8),然后递归计算 f(9) 和 f(8)。
  • 问题:f(8) 会被计算两次,f(7) 会被计算三次...大量重复计算!
  • DP 解法 :用数组 dp[i] 记录爬到第 i 级的走法数,只计算一次。

4.2 案例 1:爬楼梯(斐波那契数列)

问题:有 n 级台阶,每次可以爬 1 级或 2 级,有多少种不同的方法爬到楼顶?

状态转移方程

复制代码
dp[i] = dp[i-1] + dp[i-2]

解释:
- 最后一步走 1 级:前面有 dp[i-1] 种方法
- 最后一步走 2 级:前面有 dp[i-2] 种方法
解法 1:暴力递归(超时)
java 复制代码
/**
 * 暴力递归:时间复杂度 O(2^N),会超时
 */
public int climbStairsRecursive(int n) {
    // 终止条件
    if (n <= 2) {
        return n;  // n=1 有 1 种,n=2 有 2 种
    }
    // 递归:最后一步走 1 级或 2 级
    return climbStairsRecursive(n - 1) + climbStairsRecursive(n - 2);
}
解法 2:记忆化搜索(自顶向下)
java 复制代码
/**
 * 记忆化搜索:用数组缓存已计算的结果
 * 时间复杂度:O(N),空间复杂度:O(N)
 */
public int climbStairsMemo(int n) {
    int[] memo = new int[n + 1];
    return climbStairsHelper(n, memo);
}

private int climbStairsHelper(int n, int[] memo) {
    if (n <= 2) {
        return n;
    }
    // 如果已经计算过,直接返回
    if (memo[n] != 0) {
        return memo[n];
    }
    // 计算并缓存结果
    memo[n] = climbStairsHelper(n - 1, memo) 
            + climbStairsHelper(n - 2, memo);
    return memo[n];
}
解法 3:动态规划(自底向上,最优)
java 复制代码
/**
 * 动态规划:自底向上填表
 * 时间复杂度:O(N),空间复杂度:O(1)(滚动数组优化)
 */
public int climbStairsDP(int n) {
    if (n <= 2) {
        return n;
    }

    // 只需要保存前两个状态,不需要整个数组
    int prev2 = 1;  // dp[i-2],即爬到第 i-2 级的方法数
    int prev1 = 2;  // dp[i-1],即爬到第 i-1 级的方法数

    for (int i = 3; i <= n; i++) {
        int current = prev1 + prev2;  // dp[i] = dp[i-1] + dp[i-2]
        prev2 = prev1;  // 更新 dp[i-2]
        prev1 = current;  // 更新 dp[i-1]
    }

    return prev1;
}

4.3 案例 2:零钱兑换

问题:给定不同面额的硬币 coins 和一个总金额 amount,计算凑成总金额所需的最少硬币个数。

示例 :coins = [1, 2, 5], amount = 11
答案:11 = 5 + 5 + 1,最少 3 枚硬币。

状态定义dp[i] 表示凑成金额 i 所需的最少硬币数。

状态转移方程

复制代码
dp[i] = min(dp[i - coin] + 1)  对于所有 coin in coins 且 coin <= i

解释:
- 如果最后一枚硬币是 1,则 dp[i] = dp[i-1] + 1
- 如果最后一枚硬币是 2,则 dp[i] = dp[i-2] + 1
- 取所有可能中的最小值
java 复制代码
import java.util.Arrays;

/**
 * 零钱兑换:动态规划经典问题
 */
public class CoinChange {

    /**
     * 计算凑成金额所需的最少硬币数
     * @param coins  硬币面额数组
     * @param amount 目标金额
     * @return 最少硬币数,如果无法凑成则返回 -1
     */
    public int coinChange(int[] coins, int amount) {
        // dp[i] 表示凑成金额 i 所需的最少硬币数
        // 初始化为 amount + 1(表示无穷大,因为最多用 amount 枚 1 元硬币)
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, amount + 1);

        // 凑成金额 0 需要 0 枚硬币
        dp[0] = 0;

        // 自底向上填表
        for (int i = 1; i <= amount; i++) {
            // 尝试每一种硬币
            for (int coin : coins) {
                if (coin <= i) {
                    // 如果可以用这枚硬币,更新最小值
                    dp[i] = Math.min(dp[i], dp[i - coin] + 1);
                }
            }
        }

        // 如果 dp[amount] 还是初始值,说明无法凑成
        return dp[amount] > amount ? -1 : dp[amount];
    }

    public static void main(String[] args) {
        CoinChange solution = new CoinChange();
        int[] coins = {1, 2, 5};
        int amount = 11;
        int result = solution.coinChange(coins, amount);
        System.out.println("凑成 " + amount + " 需要最少 " + result + " 枚硬币");
        // 输出:凑成 11 需要最少 3 枚硬币
    }
}

4.4 套路:如何识别一个问题是不是 DP?

三个特征

特征 说明 示例
最优子结构 问题的最优解包含子问题的最优解 最短路径的子路径也是最短路径
重叠子问题 不同的决策序列会到达相同的子问题 爬楼梯中 f(5) 被多次计算
无后效性 当前决策只与状态有关,与如何到达该状态无关 爬到第 5 级的方法数与怎么爬到第 5 级无关

解题步骤

复制代码
1. 定义状态:dp[i] 或 dp[i][j] 代表什么?
2. 状态转移方程:dp[i] 与哪些状态有关?
3. 初始化:边界条件是什么?
4. 遍历顺序:按什么顺序填表?
5. 返回结果:最终答案在哪个状态?

五、LeetCode 高频真题解析

5.1 题目 1:两数之和(HashMap 应用)

题目:给定数组 nums 和目标值 target,找出和为 target 的两个数的索引。

暴力解法:双重循环,O(N²)。

优化解法:用 HashMap 存储已遍历的数,O(N)。

java 复制代码
import java.util.HashMap;
import java.util.Map;

/**
 * LeetCode 1: 两数之和
 * 时间复杂度:O(N)
 * 空间复杂度:O(N)
 */
public class TwoSum {

    public int[] twoSum(int[] nums, int target) {
        // key: 数字,value: 索引
        Map<Integer, Integer> map = new HashMap<>();

        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];  // 需要找的另一个数

            // 如果 complement 已经在 map 中,说明找到了
            if (map.containsKey(complement)) {
                return new int[]{map.get(complement), i};
            }

            // 将当前数加入 map
            map.put(nums[i], i);
        }

        return new int[0];  // 题目保证有解,这里不会执行
    }

    public static void main(String[] args) {
        TwoSum solution = new TwoSum();
        int[] nums = {2, 7, 11, 15};
        int target = 9;
        int[] result = solution.twoSum(nums, target);
        System.out.println("索引:[" + result[0] + ", " + result[1] + "]");
        // 输出:索引:[0, 1]
    }
}

5.2 题目 2:无重复字符的最长子串(滑动窗口)

题目:给定字符串,找出不含重复字符的最长子串的长度。

滑动窗口思想

  • 用左右两个指针表示窗口的边界。
  • 右指针向右扩展窗口,直到遇到重复字符。
  • 左指针向右收缩窗口,直到没有重复字符。
  • 记录窗口的最大长度。
java 复制代码
import java.util.HashSet;
import java.util.Set;

/**
 * LeetCode 3: 无重复字符的最长子串
 * 滑动窗口经典问题
 */
public class LongestSubstring {

    public int lengthOfLongestSubstring(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }

        Set<Character> window = new HashSet<>();  // 当前窗口内的字符
        int left = 0;   // 左指针
        int maxLen = 0; // 最大长度

        // 右指针遍历字符串
        for (int right = 0; right < s.length(); right++) {
            char c = s.charAt(right);

            // 如果字符已在窗口中,收缩左边界直到没有重复
            while (window.contains(c)) {
                window.remove(s.charAt(left));
                left++;
            }

            // 将当前字符加入窗口
            window.add(c);

            // 更新最大长度
            maxLen = Math.max(maxLen, right - left + 1);
        }

        return maxLen;
    }

    public static void main(String[] args) {
        LongestSubstring solution = new LongestSubstring();
        String s = "abcabcbb";
        int result = solution.lengthOfLongestSubstring(s);
        System.out.println("最长无重复子串长度:" + result);
        // 输出:最长无重复子串长度:3("abc")
    }
}

六、总结与学习路线

6.1 算法学习的正确姿势

复制代码
第一步:理解思想(最重要!)
  ↓ 不要急着写代码,先想清楚为什么
第二步:动手实现
  ↓ 自己写一遍,不要复制粘贴
第三步:刷题巩固
  ↓ LeetCode 按标签刷,不要随机刷
第四步:总结套路
  ↓ 形成自己的解题模板

6.2 核心思想回顾

算法思想 核心要点 经典应用
分治法 大问题分解为小问题,分别解决后合并 快速排序、归并排序
递归 函数调用自身,明确终止条件和递归逻辑 树遍历、全排列
回溯 试探 + 撤销,遍历所有可能解 八皇后、子集问题
动态规划 重叠子问题 + 最优子结构,用空间换时间 爬楼梯、背包问题
滑动窗口 维护一个可变大小的窗口,高效处理子数组/子串问题 无重复字符最长子串

6.3 推荐学习资源

LeetCode 刷题路线

  1. 入门阶段:LeetCode Hot 100(按频率排序的高频题)
  2. 进阶阶段:按标签刷题(数组、链表、树、动态规划)
  3. 冲刺阶段:剑指 Offer(国内面试必刷)

重点题单

类型 推荐题目
数组 两数之和、三数之和、盛最多水的容器
链表 反转链表、合并两个有序链表、环形链表
二叉树遍历、二叉树层序遍历、二叉搜索树验证
动态规划 爬楼梯、零钱兑换、最长递增子序列
回溯 全排列、子集、N 皇后

6.4 思维导图

复制代码
核心算法思想
├── 排序算法
│   └── 快速排序(分治法)
│       ├── 选基准、分区、递归
│       └── 三路快排(处理重复元素)
│
├── 递归与回溯
│   ├── 递归三要素
│   │   ├── 终止条件
│   │   ├── 递归逻辑
│   │   └── 状态恢复
│   └── 经典问题
│       ├── 文件树遍历
│       └── 全排列(回溯)
│
├── 动态规划
│   ├── 核心思想
│   │   └── 记住过去,避免重复计算
│   ├── 解题步骤
│   │   ├── 定义状态
│   │   ├── 状态转移方程
│   │   ├── 初始化
│   │   └── 遍历顺序
│   └── 经典问题
│       ├── 爬楼梯
│       └── 零钱兑换
│
└── LeetCode 实战
    ├── 两数之和(HashMap)
    └── 无重复字符最长子串(滑动窗口)

最后的话:算法不是刷题,是解决问题的思维。当你理解了一个算法的本质,你就能在合适的场景下自然地运用它。从暴力解法推导到最优解的过程,比答案本身更有价值。祝你在算法修炼之路上越走越远!


参考链接

相关推荐
Wanderer X5 小时前
【面试】HR
面试
AI科技星6 小时前
第二章 平行素数对网格:矩形→等腰梯形拓扑变换(完整公理终稿)
c语言·开发语言·线性代数·算法·量子计算·agi
AI视觉网奇6 小时前
blender bpy对齐物体
算法
吃好睡好便好6 小时前
在Matlab中绘制阶梯图
开发语言·人工智能·学习·算法·机器学习·matlab
Deep-w6 小时前
【MATLAB】基于 MATLAB 的离网光伏储能微电网容量优化仿真研究
开发语言·算法·matlab
闵孚龙6 小时前
Qwen3.7-Max深度解析:智能体Agent、AI编程、MCP工作流、跨框架泛化与百炼API,一次讲透国产大模型新前沿
人工智能·算法·架构·ai编程
Jasmine_llq7 小时前
《B4261 [GESP202503 三级] 2025》
开发语言·c++·算法·条件判断算法·位运算恒等式推导·简单算术运算
jiayong237 小时前
前端面试题库 - ES6+新特性篇
前端·面试·es6
简单点好不好7 小时前
工作中的工程问题: 找圆?
算法