1 介绍

1.1 进阶算法概述

01.进阶算法定义
    a.与基础算法的区别
        进阶算法在基础算法上增加了复杂度和技巧性,需要更深入的理解和更强的编码能力。
    b.主要内容
        动态规划专题、搜索进阶、图论进阶、高级数据结构。
    c.学习目标
        掌握复杂问题的建模和优化技巧,提升算法竞赛能力。

02.进阶算法分类
    a.动态规划
        背包问题、线性DP、区间DP、状态压缩DP、树形DP、数位DP。
    b.搜索优化
        剪枝、迭代加深、双向搜索、A*算法、IDA*算法。
    c.图论进阶
        最短路变形、差分约束、最近公共祖先、强连通分量、双连通分量。
    d.高级数据结构
        树状数组、线段树、可持久化数据结构、平衡树。

03.学习路线
    a.第一阶段
        掌握动态规划的基本思想和常见模型。
    b.第二阶段
        学习搜索优化技巧和图论进阶算法。
    c.第三阶段
        掌握高级数据结构的实现和应用。
    d.第四阶段
        综合运用各种算法解决复杂问题。

04.学习建议
    a.理解本质
        不要死记硬背,要理解算法的本质和适用场景。
    b.多做题目
        通过大量练习巩固知识点,形成解题思路。
    c.总结归纳
        定期总结常见题型和解题模板。
    d.参加竞赛
        通过竞赛检验学习成果,提升实战能力。

1.2 动态规划思想

01.DP核心思想
    a.最优子结构
        问题的最优解包含子问题的最优解。
    b.重叠子问题
        递归过程中会重复计算相同的子问题。
    c.无后效性
        当前状态只与之前的状态有关,与之后的决策无关。

02.DP解题框架
    a.状态定义
        确定用什么变量表示问题的状态。
    b.状态转移方程
        找出状态之间的递推关系。
    c.初始状态
        确定边界条件和初始值。
    d.计算顺序
        确保计算当前状态时所需的状态已经计算完成。

03.DP分类
    a.线性DP
        状态呈线性排列,如LIS、LCS。
    b.区间DP
        状态表示区间,如石子合并。
    c.背包DP
        物品选择问题,如01背包、完全背包。
    d.树形DP
        在树上进行DP,如树的直径。
    e.状态压缩DP
        用二进制表示状态,如TSP。
    f.数位DP
        按数位进行DP,统计满足条件的数字个数。

04.DP优化方法
    a.空间优化
        滚动数组、一维数组优化。
    b.时间优化
        单调队列优化、斜率优化、四边形不等式优化。
    c.状态优化
        减少状态维度、状态压缩。

1.3 搜索进阶

01.搜索优化概述
    a.剪枝
        通过判断提前终止不可能得到最优解的分支。
    b.记忆化
        存储已计算的状态,避免重复计算。
    c.启发式搜索
        使用估价函数指导搜索方向。

02.常见搜索优化
    a.可行性剪枝
        判断当前状态是否可行。
    b.最优性剪枝
        判断当前状态是否可能得到更优解。
    c.双向搜索
        从起点和终点同时搜索,在中间相遇。
    d.迭代加深
        限制搜索深度,逐步增加。

03.A*算法
    a.基本思想
        f(n) = g(n) + h(n),其中g(n)是起点到n的实际代价,h(n)是n到终点的估计代价。
    b.应用场景
        最短路径、八数码问题。

1.4 图论进阶

01.图论进阶内容
    a.最短路变形
        差分约束、SPFA求负环。
    b.连通性
        强连通分量、双连通分量、割点割边。
    c.树上问题
        最近公共祖先LCA、树链剖分、树上差分。
    d.网络流
        最大流、最小割、费用流。

02.图论算法优化
    a.链式前向星
        高效的图存储方式。
    b.倍增算法
        用于LCA、树上倍增。
    c.Tarjan算法
        求强连通分量、割点割边。

1.5 学习路线

01.动态规划学习路线
    a.第一阶段
        掌握背包九讲,理解01背包、完全背包、多重背包。
    b.第二阶段
        学习线性DP,如LIS、LCS、编辑距离。
    c.第三阶段
        学习区间DP和状态压缩DP。
    d.第四阶段
        学习树形DP和数位DP。

02.搜索学习路线
    a.基础搜索
        DFS、BFS、记忆化搜索。
    b.搜索优化
        剪枝技巧、双向搜索、迭代加深。
    c.启发式搜索
        A*算法、IDA*算法。

03.图论学习路线
    a.基础图论
        图的存储、遍历、最短路、最小生成树。
    b.进阶图论
        差分约束、LCA、强连通分量。
    c.高级图论
        网络流、二分图匹配。

04.数据结构学习路线
    a.基础数据结构
        树状数组、线段树。
    b.高级数据结构
        可持久化数据结构、平衡树、AC自动机。

05.学习建议
    a.循序渐进
        从简单到复杂,从单一到综合。
    b.多做题目
        每个知识点至少做5-10道题巩固。
    c.总结模板
        整理常用算法的代码模板。
    d.参加竞赛
        通过竞赛检验学习成果。

2 动态规划专题

2.1 DP基本思想

01.动态规划定义
    a.基本概念
        动态规划是通过把原问题分解为相对简单的子问题,求解子问题并存储结果,避免重复计算。
    b.核心要素
        a.状态定义
            用一个或多个变量表示问题的状态。
        b.状态转移方程
            描述状态之间的递推关系。
        c.初始状态
            边界条件或最小子问题的解。
        d.计算顺序
            保证计算当前状态时,所依赖的状态已经计算完成。

02.DP解题步骤
    a.确定状态
        分析问题,找出影响结果的关键因素。
    b.写出状态转移方程
        根据子问题的关系推导递推公式。
    c.确定初始状态和边界
        找出最小子问题的解。
    d.确定计算顺序
        通常是从小到大,从简单到复杂。
    e.优化空间复杂度
        使用滚动数组等技巧优化空间。

03.DP与递归的关系
    a.记忆化搜索
        在递归的基础上加上记忆化,避免重复计算。
    b.递推
        从初始状态开始,按顺序计算所有状态。
    c.两者对比
        记忆化搜索代码简洁但可能栈溢出,递推效率高但需要考虑计算顺序。

04.DP优化技巧
    a.空间优化
        使用滚动数组,将二维数组优化为一维。
    b.时间优化
        单调队列优化、斜率优化、四边形不等式优化。
    c.状态压缩
        使用二进制表示状态,减少状态数量。

2.2 背包问题

01.01背包
    a.问题描述
        有n个物品,每个物品有重量w[i]和价值v[i],背包容量为V,每个物品最多选一次,求最大价值。
    b.状态定义
        dp[i][j]表示前i个物品,背包容量为j时的最大价值。
    c.状态转移
        dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])
    d.Java实现
        ---
        public int knapsack01(int[] w, int[] v, int V) {
            int n = w.length;
            int[][] dp = new int[n + 1][V + 1];

            for (int i = 1; i <= n; i++) {
                for (int j = 0; j <= V; j++) {
                    dp[i][j] = dp[i-1][j];
                    if (j >= w[i-1]) {
                        dp[i][j] = Math.max(dp[i][j], dp[i-1][j-w[i-1]] + v[i-1]);
                    }
                }
            }
            return dp[n][V];
        }
        ---
    e.空间优化
        ---
        public int knapsack01Optimized(int[] w, int[] v, int V) {
            int[] dp = new int[V + 1];
            for (int i = 0; i < w.length; i++) {
                for (int j = V; j >= w[i]; j--) {
                    dp[j] = Math.max(dp[j], dp[j-w[i]] + v[i]);
                }
            }
            return dp[V];
        }
        ---

02.完全背包
    a.问题描述
        每个物品可以选择无限次。
    b.状态转移
        dp[i][j] = max(dp[i-1][j], dp[i][j-w[i]] + v[i])
    c.空间优化
        ---
        public int knapsackComplete(int[] w, int[] v, int V) {
            int[] dp = new int[V + 1];
            for (int i = 0; i < w.length; i++) {
                for (int j = w[i]; j <= V; j++) {
                    dp[j] = Math.max(dp[j], dp[j-w[i]] + v[i]);
                }
            }
            return dp[V];
        }
        ---

03.多重背包
    a.问题描述
        每个物品有数量限制s[i]。
    b.二进制优化
        ---
        public int knapsackMultiple(int[] w, int[] v, int[] s, int V) {
            List<Integer> weights = new ArrayList<>();
            List<Integer> values = new ArrayList<>();

            // 二进制拆分
            for (int i = 0; i < w.length; i++) {
                int num = s[i];
                for (int k = 1; k <= num; k *= 2) {
                    weights.add(k * w[i]);
                    values.add(k * v[i]);
                    num -= k;
                }
                if (num > 0) {
                    weights.add(num * w[i]);
                    values.add(num * v[i]);
                }
            }

            // 转化为01背包
            int[] dp = new int[V + 1];
            for (int i = 0; i < weights.size(); i++) {
                for (int j = V; j >= weights.get(i); j--) {
                    dp[j] = Math.max(dp[j], dp[j-weights.get(i)] + values.get(i));
                }
            }
            return dp[V];
        }
        ---

04.分组背包
    a.问题描述
        物品分为若干组,每组最多选一个。
    b.状态转移
        ---
        public int knapsackGroup(int[][][] items, int V) {
            int[] dp = new int[V + 1];

            for (int[][] group : items) {
                for (int j = V; j >= 0; j--) {
                    for (int[] item : group) {
                        int w = item[0], v = item[1];
                        if (j >= w) {
                            dp[j] = Math.max(dp[j], dp[j-w] + v);
                        }
                    }
                }
            }
            return dp[V];
        }
        ---

2.3 线性DP

01.最长上升子序列LIS
    a.问题描述
        给定序列,求最长严格上升子序列的长度。
    b.O(n²)解法
        ---
        public int lengthOfLIS(int[] nums) {
            int n = nums.length;
            int[] dp = new int[n];
            Arrays.fill(dp, 1);
            int maxLen = 1;

            for (int i = 1; i < n; 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;
        }
        ---
    c.O(n log n)解法
        ---
        public int lengthOfLIS(int[] nums) {
            int[] tails = new int[nums.length];
            int len = 0;

            for (int num : nums) {
                int left = 0, right = len;
                while (left < right) {
                    int mid = left + right >> 1;
                    if (tails[mid] < num) left = mid + 1;
                    else right = mid;
                }
                tails[left] = num;
                if (left == len) len++;
            }
            return len;
        }
        ---

02.最长公共子序列LCS
    a.问题描述
        给定两个序列,求最长公共子序列的长度。
    b.状态定义
        dp[i][j]表示s1前i个字符和s2前j个字符的LCS长度。
    c.状态转移
        ---
        public int longestCommonSubsequence(String s1, String s2) {
            int m = s1.length(), n = s2.length();
            int[][] dp = new int[m + 1][n + 1];

            for (int i = 1; i <= m; i++) {
                for (int j = 1; j <= n; j++) {
                    if (s1.charAt(i-1) == s2.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];
        }
        ---

03.编辑距离
    a.问题描述
        给定两个字符串,求将一个字符串转换为另一个字符串的最少操作次数。
    b.操作类型
        插入、删除、替换。
    c.状态转移
        ---
        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] + 1,    // 删除
                            dp[i][j-1] + 1),   // 插入
                            dp[i-1][j-1] + 1   // 替换
                        );
                    }
                }
            }
            return dp[m][n];
        }
        ---

04.最大子数组和
    a.问题描述
        给定数组,求连续子数组的最大和。
    b.Kadane算法
        ---
        public int maxSubArray(int[] nums) {
            int maxSum = nums[0];
            int currentSum = nums[0];

            for (int i = 1; i < nums.length; i++) {
                currentSum = Math.max(nums[i], currentSum + nums[i]);
                maxSum = Math.max(maxSum, currentSum);
            }
            return maxSum;
        }
        ---

2.4 区间DP

01.区间DP概述
    a.定义
        状态表示一个区间,通过合并小区间得到大区间的最优解。
    b.状态定义
        dp[i][j]表示区间[i,j]的最优值。
    c.状态转移
        枚举区间的分割点k,dp[i][j] = min/max(dp[i][k] + dp[k+1][j] + cost)。

02.石子合并
    a.问题描述
        有n堆石子,每次合并相邻两堆,代价为两堆石子数量之和,求最小总代价。
    b.状态定义
        dp[i][j]表示合并区间[i,j]的最小代价。
    c.Java实现
        ---
        public int mergeStones(int[] stones) {
            int n = stones.length;
            int[] sum = new int[n + 1];
            for (int i = 1; i <= n; i++) {
                sum[i] = sum[i-1] + stones[i-1];
            }

            int[][] dp = new int[n][n];

            for (int len = 2; len <= n; len++) {
                for (int i = 0; i + len - 1 < n; i++) {
                    int j = i + len - 1;
                    dp[i][j] = Integer.MAX_VALUE;
                    for (int k = i; k < j; k++) {
                        dp[i][j] = Math.min(dp[i][j],
                            dp[i][k] + dp[k+1][j] + sum[j+1] - sum[i]);
                    }
                }
            }
            return dp[0][n-1];
        }
        ---

03.最长回文子序列
    a.问题描述
        LeetCode 516 - 找出字符串中最长的回文子序列。
    b.状态定义
        dp[i][j]表示s[i..j]的最长回文子序列长度。
    c.状态转移
        ---
        public int longestPalindromeSubseq(String s) {
            int n = s.length();
            int[][] dp = new int[n][n];

            for (int i = n - 1; i >= 0; i--) {
                dp[i][i] = 1;
                for (int j = i + 1; j < n; j++) {
                    if (s.charAt(i) == s.charAt(j)) {
                        dp[i][j] = dp[i+1][j-1] + 2;
                    } else {
                        dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
                    }
                }
            }
            return dp[0][n-1];
        }
        ---

04.戳气球
    a.问题描述
        LeetCode 312 - 戳破气球获得最大硬币数。
    b.区间DP解法
        ---
        public int maxCoins(int[] nums) {
            int n = nums.length;
            int[] arr = new int[n + 2];
            arr[0] = arr[n + 1] = 1;
            for (int i = 1; i <= n; i++) {
                arr[i] = nums[i-1];
            }

            int[][] dp = new int[n + 2][n + 2];

            for (int len = 3; len <= n + 2; len++) {
                for (int i = 0; i + len - 1 <= n + 1; i++) {
                    int j = i + len - 1;
                    for (int k = i + 1; k < j; k++) {
                        dp[i][j] = Math.max(dp[i][j],
                            dp[i][k] + dp[k][j] + arr[i] * arr[k] * arr[j]);
                    }
                }
            }
            return dp[0][n+1];
        }
        ---

2.5 状态压缩DP

01.状态压缩概述
    a.基本思想
        使用二进制数表示状态,将多维状态压缩为一维。
    b.适用场景
        状态数量较少(通常n≤20),状态之间有明确的转移关系。
    c.常用技巧
        位运算、枚举子集、预处理状态。

02.旅行商问题TSP
    a.问题描述
        给定n个城市和城市间的距离,求访问所有城市恰好一次并回到起点的最短路径。
    b.状态定义
        dp[state][i]表示已访问城市集合为state,当前在城市i的最短路径。
    c.Java实现
        ---
        public int tsp(int[][] dist) {
            int n = dist.length;
            int[][] dp = new int[1 << n][n];

            for (int[] row : dp) Arrays.fill(row, Integer.MAX_VALUE / 2);
            dp[1][0] = 0;

            for (int state = 1; state < (1 << n); state++) {
                for (int i = 0; i < n; i++) {
                    if ((state & (1 << i)) == 0) continue;

                    for (int j = 0; j < n; j++) {
                        if ((state & (1 << j)) != 0) continue;
                        int newState = state | (1 << j);
                        dp[newState][j] = Math.min(dp[newState][j],
                                                   dp[state][i] + dist[i][j]);
                    }
                }
            }

            int ans = Integer.MAX_VALUE;
            for (int i = 1; i < n; i++) {
                ans = Math.min(ans, dp[(1 << n) - 1][i] + dist[i][0]);
            }
            return ans;
        }
        ---

03.集合DP
    a.枚举子集
        ---
        // 枚举state的所有子集
        for (int sub = state; sub > 0; sub = (sub - 1) & state) {
            // 处理子集sub
        }
        ---
    b.状态转移
        ---
        // 从子集转移到全集
        for (int state = 0; state < (1 << n); state++) {
            for (int i = 0; i < n; i++) {
                if ((state & (1 << i)) != 0) {
                    int prevState = state ^ (1 << i);
                    dp[state] = Math.min(dp[state], dp[prevState] + cost[i]);
                }
            }
        }
        ---

04.经典题目
    a.最短Hamilton路径
        AcWing 91 - 使用状态压缩DP求解。
    b.炮兵阵地
        POJ 1185 - 多行状态压缩DP。

2.6 树形DP

01.树形DP概述
    a.定义
        在树上进行动态规划,状态定义在树的节点上。
    b.特点
        通常使用DFS遍历树,从叶子节点向根节点转移状态。
    c.常见问题
        树的直径、树的重心、树上背包。

02.树的直径
    a.问题描述
        求树中最长路径的长度。
    b.两次DFS解法
        ---
        List<Integer>[] graph;
        int maxDist = 0;

        public int treeDiameter(int[][] edges) {
            int n = edges.length + 1;
            graph = new ArrayList[n];
            for (int i = 0; i < n; i++) {
                graph[i] = new ArrayList<>();
            }
            for (int[] edge : edges) {
                graph[edge[0]].add(edge[1]);
                graph[edge[1]].add(edge[0]);
            }

            dfs(0, -1);
            return maxDist;
        }

        private int dfs(int u, int parent) {
            int max1 = 0, max2 = 0;
            for (int v : graph[u]) {
                if (v == parent) continue;
                int depth = dfs(v, u) + 1;
                if (depth > max1) {
                    max2 = max1;
                    max1 = depth;
                } else if (depth > max2) {
                    max2 = depth;
                }
            }
            maxDist = Math.max(maxDist, max1 + max2);
            return max1;
        }
        ---

03.树的重心
    a.问题描述
        找到一个节点,删除后剩余各连通块的最大值最小。
    b.DFS解法
        ---
        int n, minMax = Integer.MAX_VALUE;
        List<Integer>[] graph;

        public int dfs(int u, int parent) {
            int size = 1, maxPart = 0;
            for (int v : graph[u]) {
                if (v == parent) continue;
                int subSize = dfs(v, u);
                size += subSize;
                maxPart = Math.max(maxPart, subSize);
            }
            maxPart = Math.max(maxPart, n - size);
            minMax = Math.min(minMax, maxPart);
            return size;
        }
        ---

04.树上背包
    a.问题描述
        在树上选择若干个节点,使得总价值最大且满足约束条件。
    b.状态定义
        dp[u][j]表示以u为根的子树中选择j个节点的最大价值。

2.7 数位DP

01.数位DP概述
    a.定义
        按数位进行动态规划,通常用于统计满足某种条件的数字个数。
    b.适用场景
        统计区间[L,R]内满足条件的数字个数。
    c.基本思路
        将问题转化为求[0,R]和[0,L-1]的答案之差。

02.数位DP模板
    a.记忆化搜索
        ---
        int[][] dp;
        String num;

        public int countDigits(int n) {
            num = String.valueOf(n);
            dp = new int[num.length()][10];
            for (int[] row : dp) Arrays.fill(row, -1);
            return dfs(0, 0, true, true);
        }

        private int dfs(int pos, int sum, boolean isLimit, boolean isNum) {
            if (pos == num.length()) {
                return isNum ? 1 : 0;
            }

            if (!isLimit && isNum && dp[pos][sum] != -1) {
                return dp[pos][sum];
            }

            int res = 0;
            if (!isNum) {
                res = dfs(pos + 1, sum, false, false);
            }

            int up = isLimit ? num.charAt(pos) - '0' : 9;
            int low = isNum ? 0 : 1;

            for (int d = low; d <= up; d++) {
                res += dfs(pos + 1, sum + d, isLimit && d == up, true);
            }

            if (!isLimit && isNum) {
                dp[pos][sum] = res;
            }
            return res;
        }
        ---

03.不含连续1的非负整数
    a.问题描述
        LeetCode 600 - 统计[0,n]中二进制表示不含连续1的数字个数。
    b.数位DP解法
        ---
        public int findIntegers(int n) {
            String binary = Integer.toBinaryString(n);
            int m = binary.length();
            int[][] dp = new int[m][2];
            for (int[] row : dp) Arrays.fill(row, -1);
            return dfs(0, 0, true, binary, dp);
        }

        private int dfs(int pos, int pre, boolean isLimit, String s, int[][] dp) {
            if (pos == s.length()) return 1;

            if (!isLimit && dp[pos][pre] != -1) {
                return dp[pos][pre];
            }

            int up = isLimit ? s.charAt(pos) - '0' : 1;
            int res = 0;

            for (int d = 0; d <= up; d++) {
                if (pre == 1 && d == 1) continue;
                res += dfs(pos + 1, d, isLimit && d == up, s, dp);
            }

            if (!isLimit) {
                dp[pos][pre] = res;
            }
            return res;
        }
        ---

04.数字1的个数
    a.问题描述
        LeetCode 233 - 统计[1,n]中数字1出现的次数。
    b.数位DP解法
        按位统计每一位上1出现的次数。

2.8 DP优化技巧

01.单调队列优化
    a.适用场景
        状态转移方程中有max/min(dp[j])的形式,且j的范围是一个滑动窗口。
    b.多重背包优化
        ---
        public int multipleKnapsack(int[] w, int[] v, int[] s, int V) {
            int[] dp = new int[V + 1];
            int[] q = new int[V + 1];

            for (int i = 0; i < w.length; i++) {
                for (int r = 0; r < w[i]; r++) {
                    int head = 0, tail = -1;
                    for (int j = r; j <= V; j += w[i]) {
                        int k = (j - r) / w[i];
                        while (head <= tail && k - q[head] > s[i]) head++;

                        if (head <= tail) {
                            dp[j] = Math.max(dp[j], dp[q[head] * w[i] + r] +
                                (k - q[head]) * v[i]);
                        }

                        while (head <= tail &&
                               dp[q[tail] * w[i] + r] - q[tail] * v[i] <=
                               dp[j] - k * v[i]) {
                            tail--;
                        }
                        q[++tail] = k;
                    }
                }
            }
            return dp[V];
        }
        ---

02.斜率优化
    a.适用场景
        状态转移方程可以转化为斜率形式。
    b.基本思想
        维护一个下凸壳,使用二分或单调队列优化。

03.四边形不等式优化
    a.适用条件
        满足区间包含单调性和四边形不等式。
    b.优化效果
        将O(n³)优化为O(n²)。

04.矩阵快速幂优化
    a.适用场景
        线性递推关系,如斐波那契数列。
    b.实现
        ---
        public long[][] matrixMultiply(long[][] A, long[][] B, long mod) {
            int n = A.length;
            long[][] C = new long[n][n];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    for (int k = 0; k < n; k++) {
                        C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % mod;
                    }
                }
            }
            return C;
        }

        public long[][] matrixPow(long[][] A, long n, long mod) {
            int size = A.length;
            long[][] res = new long[size][size];
            for (int i = 0; i < size; i++) res[i][i] = 1;

            while (n > 0) {
                if ((n & 1) == 1) res = matrixMultiply(res, A, mod);
                A = matrixMultiply(A, A, mod);
                n >>= 1;
            }
            return res;
        }
        ---

3 搜索进阶

3.1 Flood Fill算法

01.Flood Fill概述
    a.定义
        从某个起点开始,将连通的区域染成同一颜色。
    b.实现方式
        DFS或BFS。
    c.应用场景
        图像填充、连通块统计、岛屿问题。

02.图像渲染
    a.问题描述
        LeetCode 733 - 从起始像素开始,将连通的相同颜色像素改为新颜色。
    b.DFS实现
        ---
        int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};

        public int[][] floodFill(int[][] image, int sr, int sc, int color) {
            int oldColor = image[sr][sc];
            if (oldColor == color) return image;
            dfs(image, sr, sc, oldColor, color);
            return image;
        }

        private void dfs(int[][] image, int r, int c, int oldColor, int newColor) {
            if (r < 0 || r >= image.length || c < 0 || c >= image[0].length) {
                return;
            }
            if (image[r][c] != oldColor) return;

            image[r][c] = newColor;
            for (int[] dir : dirs) {
                dfs(image, r + dir[0], c + dir[1], oldColor, newColor);
            }
        }
        ---

03.岛屿数量
    a.问题描述
        LeetCode 200 - 统计二维网格中岛屿的数量。
    b.DFS解法
        ---
        public int numIslands(char[][] grid) {
            int count = 0;
            for (int i = 0; i < grid.length; i++) {
                for (int j = 0; j < grid[0].length; j++) {
                    if (grid[i][j] == '1') {
                        dfs(grid, i, j);
                        count++;
                    }
                }
            }
            return count;
        }

        private void dfs(char[][] grid, int i, int j) {
            if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) {
                return;
            }
            if (grid[i][j] != '1') return;

            grid[i][j] = '0';
            dfs(grid, i-1, j);
            dfs(grid, i+1, j);
            dfs(grid, i, j-1);
            dfs(grid, i, j+1);
        }
        ---

04.岛屿的最大面积
    a.问题描述
        LeetCode 695 - 找出最大岛屿的面积。
    b.DFS解法
        ---
        public int maxAreaOfIsland(int[][] grid) {
            int maxArea = 0;
            for (int i = 0; i < grid.length; i++) {
                for (int j = 0; j < grid[0].length; j++) {
                    if (grid[i][j] == 1) {
                        maxArea = Math.max(maxArea, dfs(grid, i, j));
                    }
                }
            }
            return maxArea;
        }

        private int dfs(int[][] grid, int i, int j) {
            if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) {
                return 0;
            }
            if (grid[i][j] != 1) return 0;

            grid[i][j] = 0;
            return 1 + dfs(grid, i-1, j) + dfs(grid, i+1, j) +
                   dfs(grid, i, j-1) + dfs(grid, i, j+1);
        }
        ---

3.2 最短路模型

01.最短路模型概述
    a.定义
        将问题抽象为图的最短路问题。
    b.常见模型
        状态转移、迷宫问题、最小步数。

02.单词接龙
    a.问题描述
        LeetCode 127 - 从起始单词变换到目标单词的最短路径长度。
    b.BFS解法
        ---
        public int ladderLength(String beginWord, String endWord, List<String> wordList) {
            Set<String> wordSet = new HashSet<>(wordList);
            if (!wordSet.contains(endWord)) return 0;

            Queue<String> queue = new LinkedList<>();
            queue.offer(beginWord);
            int steps = 1;

            while (!queue.isEmpty()) {
                int size = queue.size();
                for (int i = 0; i < size; i++) {
                    String word = queue.poll();
                    if (word.equals(endWord)) return steps;

                    char[] chars = word.toCharArray();
                    for (int j = 0; j < chars.length; j++) {
                        char old = chars[j];
                        for (char c = 'a'; c <= 'z'; c++) {
                            chars[j] = c;
                            String newWord = new String(chars);
                            if (wordSet.contains(newWord)) {
                                queue.offer(newWord);
                                wordSet.remove(newWord);
                            }
                        }
                        chars[j] = old;
                    }
                }
                steps++;
            }
            return 0;
        }
        ---

03.打开转盘锁
    a.问题描述
        LeetCode 752 - 从0000转到目标状态的最少步数。
    b.BFS解法
        ---
        public int openLock(String[] deadends, String target) {
            Set<String> dead = new HashSet<>(Arrays.asList(deadends));
            Set<String> visited = new HashSet<>();
            Queue<String> queue = new LinkedList<>();

            if (dead.contains("0000")) return -1;

            queue.offer("0000");
            visited.add("0000");
            int steps = 0;

            while (!queue.isEmpty()) {
                int size = queue.size();
                for (int i = 0; i < size; i++) {
                    String cur = queue.poll();
                    if (cur.equals(target)) return steps;
                    if (dead.contains(cur)) continue;

                    for (int j = 0; j < 4; j++) {
                        String up = plusOne(cur, j);
                        String down = minusOne(cur, j);

                        if (!visited.contains(up)) {
                            queue.offer(up);
                            visited.add(up);
                        }
                        if (!visited.contains(down)) {
                            queue.offer(down);
                            visited.add(down);
                        }
                    }
                }
                steps++;
            }
            return -1;
        }

        private String plusOne(String s, int i) {
            char[] chars = s.toCharArray();
            chars[i] = chars[i] == '9' ? '0' : (char)(chars[i] + 1);
            return new String(chars);
        }

        private String minusOne(String s, int i) {
            char[] chars = s.toCharArray();
            chars[i] = chars[i] == '0' ? '9' : (char)(chars[i] - 1);
            return new String(chars);
        }
        ---

3.3 多源BFS

01.多源BFS概述
    a.定义
        从多个起点同时开始BFS。
    b.实现方式
        将所有起点同时加入队列。
    c.应用场景
        多源最短路、腐烂的橘子。

02.01矩阵
    a.问题描述
        LeetCode 542 - 找出每个格子到最近0的距离。
    b.多源BFS解法
        ---
        public int[][] updateMatrix(int[][] mat) {
            int m = mat.length, n = mat[0].length;
            int[][] dist = new int[m][n];
            boolean[][] visited = new boolean[m][n];
            Queue<int[]> queue = new LinkedList<>();

            // 将所有0加入队列
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if (mat[i][j] == 0) {
                        queue.offer(new int[]{i, j});
                        visited[i][j] = true;
                    }
                }
            }

            int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
            while (!queue.isEmpty()) {
                int[] cur = queue.poll();
                int x = cur[0], y = cur[1];

                for (int[] dir : dirs) {
                    int nx = x + dir[0], ny = y + dir[1];
                    if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx][ny]) {
                        dist[nx][ny] = dist[x][y] + 1;
                        visited[nx][ny] = true;
                        queue.offer(new int[]{nx, ny});
                    }
                }
            }
            return dist;
        }
        ---

03.腐烂的橘子
    a.问题描述
        LeetCode 994 - 计算所有橘子腐烂需要的最少时间。
    b.多源BFS解法
        ---
        public int orangesRotting(int[][] grid) {
            int m = grid.length, n = grid[0].length;
            Queue<int[]> queue = new LinkedList<>();
            int fresh = 0;

            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if (grid[i][j] == 2) {
                        queue.offer(new int[]{i, j});
                    } else if (grid[i][j] == 1) {
                        fresh++;
                    }
                }
            }

            if (fresh == 0) return 0;

            int minutes = 0;
            int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};

            while (!queue.isEmpty()) {
                int size = queue.size();
                for (int i = 0; i < size; i++) {
                    int[] cur = queue.poll();
                    for (int[] dir : dirs) {
                        int nx = cur[0] + dir[0];
                        int ny = cur[1] + dir[1];
                        if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1) {
                            grid[nx][ny] = 2;
                            fresh--;
                            queue.offer(new int[]{nx, ny});
                        }
                    }
                }
                minutes++;
            }

            return fresh == 0 ? minutes - 1 : -1;
        }
        ---

3.4 双向搜索

01.双向搜索概述
    a.基本思想
        从起点和终点同时开始搜索,在中间相遇时结束。
    b.优势
        搜索空间从O(b^d)降低到O(2*b^(d/2)),其中b是分支因子,d是深度。
    c.适用场景
        起点和终点明确,搜索空间很大但深度较小。

02.双向BFS
    a.实现思路
        使用两个队列分别从起点和终点搜索,每次扩展较小的队列。
    b.Java实现
        ---
        public int bidirectionalBFS(String start, String end) {
            if (start.equals(end)) return 0;

            Set<String> visited1 = new HashSet<>();
            Set<String> visited2 = new HashSet<>();
            Set<String> set1 = new HashSet<>();
            Set<String> set2 = new HashSet<>();

            set1.add(start);
            set2.add(end);
            visited1.add(start);
            visited2.add(end);

            int steps = 0;

            while (!set1.isEmpty() && !set2.isEmpty()) {
                // 总是扩展较小的集合
                if (set1.size() > set2.size()) {
                    Set<String> temp = set1;
                    set1 = set2;
                    set2 = temp;
                    temp = visited1;
                    visited1 = visited2;
                    visited2 = temp;
                }

                Set<String> next = new HashSet<>();
                steps++;

                for (String cur : set1) {
                    for (String neighbor : getNeighbors(cur)) {
                        if (set2.contains(neighbor)) {
                            return steps;
                        }
                        if (!visited1.contains(neighbor)) {
                            visited1.add(neighbor);
                            next.add(neighbor);
                        }
                    }
                }
                set1 = next;
            }
            return -1;
        }
        ---

03.单词接龙II
    a.问题描述
        LeetCode 126 - 找出所有最短转换序列。
    b.双向BFS+DFS
        先用双向BFS找到最短路径长度,再用DFS构造所有路径。

3.5 A*算法

01.A*算法概述
    a.基本思想
        使用估价函数f(n) = g(n) + h(n)指导搜索,其中g(n)是起点到n的实际代价,h(n)是n到终点的估计代价。
    b.启发函数h(n)
        必须满足可采纳性,即h(n)不能高估实际代价。
    c.优势
        相比Dijkstra,A*使用启发函数加速搜索。

02.A*算法实现
    a.Java实现
        ---
        class Node {
            int x, y;
            int g, h;  // g:起点到当前点的代价, h:当前点到终点的估计

            int f() { return g + h; }
        }

        public int astar(int[][] grid, int[] start, int[] end) {
            int m = grid.length, n = grid[0].length;
            PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.f() - b.f());
            boolean[][] visited = new boolean[m][n];

            Node startNode = new Node();
            startNode.x = start[0];
            startNode.y = start[1];
            startNode.g = 0;
            startNode.h = heuristic(start, end);
            pq.offer(startNode);

            int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};

            while (!pq.isEmpty()) {
                Node cur = pq.poll();

                if (cur.x == end[0] && cur.y == end[1]) {
                    return cur.g;
                }

                if (visited[cur.x][cur.y]) continue;
                visited[cur.x][cur.y] = true;

                for (int[] dir : dirs) {
                    int nx = cur.x + dir[0];
                    int ny = cur.y + dir[1];

                    if (nx >= 0 && nx < m && ny >= 0 && ny < n &&
                        grid[nx][ny] == 0 && !visited[nx][ny]) {
                        Node next = new Node();
                        next.x = nx;
                        next.y = ny;
                        next.g = cur.g + 1;
                        next.h = heuristic(new int[]{nx, ny}, end);
                        pq.offer(next);
                    }
                }
            }
            return -1;
        }

        private int heuristic(int[] a, int[] b) {
            return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]);  // 曼哈顿距离
        }
        ---

03.启发函数选择
    a.曼哈顿距离
        适用于四方向移动的网格。
    b.欧几里得距离
        适用于任意方向移动。
    c.切比雪夫距离
        适用于八方向移动。

3.6 剪枝优化

01.剪枝类型
    a.可行性剪枝
        判断当前状态是否违反约束条件,提前返回。
    b.最优性剪枝
        判断当前状态是否可能产生更优解,不可能则剪枝。
    c.重复状态剪枝
        使用visited数组或哈希表避免重复访问。

02.N皇后剪枝
    a.问题描述
        在n×n棋盘上放置n个皇后,使得任意两个皇后不在同一行、列、对角线。
    b.剪枝优化
        ---
        boolean[] col, dg, udg;
        int n, count;

        public int totalNQueens(int n) {
            this.n = n;
            col = new boolean[n];
            dg = new boolean[2 * n];
            udg = new boolean[2 * n];
            count = 0;
            dfs(0);
            return count;
        }

        private void dfs(int row) {
            if (row == n) {
                count++;
                return;
            }

            for (int i = 0; i < n; i++) {
                // 可行性剪枝
                if (!col[i] && !dg[row + i] && !udg[n - row + i]) {
                    col[i] = dg[row + i] = udg[n - row + i] = true;
                    dfs(row + 1);
                    col[i] = dg[row + i] = udg[n - row + i] = false;
                }
            }
        }
        ---

03.数独求解剪枝
    a.问题描述
        LeetCode 37 - 解数独。
    b.剪枝策略
        a.行列宫剪枝
            使用三个二维数组记录每行、每列、每宫已使用的数字。
        b.优先填充候选数少的格子
            减少搜索分支。

3.7 迭代加深

01.迭代加深概述
    a.基本思想
        限制搜索深度,从小到大逐步增加深度限制,直到找到解。
    b.优势
        结合DFS的空间优势和BFS的最优性。
    c.适用场景
        解的深度未知,但解的深度不会太大。

02.迭代加深实现
    a.框架
        ---
        public boolean iterativeDeepening() {
            for (int maxDepth = 0; maxDepth <= MAX_DEPTH; maxDepth++) {
                if (dfs(start, 0, maxDepth)) {
                    return true;
                }
            }
            return false;
        }

        private boolean dfs(State state, int depth, int maxDepth) {
            if (depth > maxDepth) return false;
            if (isGoal(state)) return true;

            for (State next : getNextStates(state)) {
                if (dfs(next, depth + 1, maxDepth)) {
                    return true;
                }
            }
            return false;
        }
        ---

03.加成序列
    a.问题描述
        AcWing 170 - 构造加成序列,使得每个数都是前面两个数之和。
    b.迭代加深+剪枝
        ---
        int[] path;
        int n;

        public boolean dfs(int depth, int maxDepth) {
            if (depth > maxDepth) return false;
            if (path[depth - 1] == n) return true;

            // 最优性剪枝
            if ((n - path[depth - 1]) * (1 << (maxDepth - depth)) < path[depth - 1]) {
                return false;
            }

            Set<Integer> set = new HashSet<>();
            for (int i = depth - 1; i >= 0; i--) {
                for (int j = i; j >= 0; j--) {
                    int sum = path[i] + path[j];
                    if (sum > n || sum <= path[depth - 1] || set.contains(sum)) {
                        continue;
                    }
                    set.add(sum);
                    path[depth] = sum;
                    if (dfs(depth + 1, maxDepth)) {
                        return true;
                    }
                }
            }
            return false;
        }
        ---

3.8 IDA*算法

01.IDA*算法概述
    a.定义
        迭代加深+A*算法的结合,使用估价函数指导迭代加深。
    b.估价函数
        f(n) = g(n) + h(n),其中g(n)是当前深度,h(n)是估计的剩余步数。
    c.优势
        空间复杂度低,能找到最优解。

02.IDA*算法实现
    a.框架
        ---
        int maxDepth;

        public int idastar() {
            int h = heuristic(start);
            for (maxDepth = h; maxDepth <= MAX; maxDepth++) {
                if (dfs(start, 0)) {
                    return maxDepth;
                }
            }
            return -1;
        }

        private boolean dfs(State state, int depth) {
            int h = heuristic(state);
            if (depth + h > maxDepth) return false;
            if (isGoal(state)) return true;

            for (State next : getNextStates(state)) {
                if (dfs(next, depth + 1)) {
                    return true;
                }
            }
            return false;
        }
        ---

03.八数码问题
    a.问题描述
        3×3网格中有8个数字和1个空格,通过移动空格使得数字按顺序排列。
    b.IDA*解法
        ---
        public int slidingPuzzle(int[][] board) {
            String start = boardToString(board);
            String target = "123456780";

            if (start.equals(target)) return 0;

            int h = manhattan(start, target);
            for (int maxDepth = h; maxDepth <= 50; maxDepth++) {
                if (dfs(start, target, 0, maxDepth, -1)) {
                    return maxDepth;
                }
            }
            return -1;
        }

        private boolean dfs(String state, String target, int depth, int maxDepth, int last) {
            if (depth + manhattan(state, target) > maxDepth) return false;
            if (state.equals(target)) return true;

            int zero = state.indexOf('0');
            int x = zero / 3, y = zero % 3;
            int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};

            for (int i = 0; i < 4; i++) {
                if (i + last == 3) continue;  // 避免回到上一步

                int nx = x + dirs[i][0];
                int ny = y + dirs[i][1];
                if (nx >= 0 && nx < 3 && ny >= 0 && ny < 3) {
                    String next = swap(state, zero, nx * 3 + ny);
                    if (dfs(next, target, depth + 1, maxDepth, i)) {
                        return true;
                    }
                }
            }
            return false;
        }

        private int manhattan(String state, String target) {
            int dist = 0;
            for (int i = 0; i < 9; i++) {
                if (state.charAt(i) != '0') {
                    int num = state.charAt(i) - '0';
                    int targetPos = target.indexOf(state.charAt(i));
                    dist += Math.abs(i / 3 - targetPos / 3) + Math.abs(i % 3 - targetPos % 3);
                }
            }
            return dist;
        }
        ---

4 图论进阶

4.1 单源最短路综合

01.Dijkstra变形
    a.路径记录
        使用pre数组记录前驱节点,回溯构造路径。
        ---
        int[] dist, pre;

        public List<Integer> dijkstraPath(int start, int end) {
            dijkstra(start);
            List<Integer> path = new ArrayList<>();
            for (int u = end; u != -1; u = pre[u]) {
                path.add(u);
            }
            Collections.reverse(path);
            return path;
        }
        ---
    b.次短路
        维护最短路和次短路两个距离数组。
    c.限制边数
        使用Bellman-Ford算法,限制松弛次数。

02.SPFA算法
    a.基本思想
        Bellman-Ford的队列优化,只有距离更新的节点才加入队列。
    b.Java实现
        ---
        public int spfa(int start, int n) {
            int[] dist = new int[n];
            boolean[] inQueue = new boolean[n];
            Arrays.fill(dist, Integer.MAX_VALUE);
            dist[start] = 0;

            Queue<Integer> queue = new LinkedList<>();
            queue.offer(start);
            inQueue[start] = true;

            while (!queue.isEmpty()) {
                int u = queue.poll();
                inQueue[u] = false;

                for (int i = h[u]; i != -1; i = ne[i]) {
                    int v = e[i];
                    if (dist[v] > dist[u] + w[i]) {
                        dist[v] = dist[u] + w[i];
                        if (!inQueue[v]) {
                            queue.offer(v);
                            inQueue[v] = true;
                        }
                    }
                }
            }
            return dist[n - 1];
        }
        ---

03.网络延迟时间
    a.问题描述
        LeetCode 743 - 从节点k发出信号,求所有节点接收到信号的最短时间。
    b.Dijkstra解法
        单源最短路,求最大距离。

4.2 Floyd算法

01.Floyd算法原理
    a.基本思想
        动态规划思想,枚举中间节点k,更新任意两点间的最短路。
    b.状态转移
        dp[i][j][k] = min(dp[i][j][k-1], dp[i][k][k-1] + dp[k][j][k-1])
    c.时间复杂度
        O(n³),适合稠密图和小规模图。

02.Floyd算法实现
    a.Java实现
        ---
        public void floyd(int[][] dist, int n) {
            for (int k = 0; k < n; k++) {
                for (int i = 0; i < n; i++) {
                    for (int j = 0; j < n; j++) {
                        if (dist[i][k] != Integer.MAX_VALUE &&
                            dist[k][j] != Integer.MAX_VALUE) {
                            dist[i][j] = Math.min(dist[i][j],
                                                 dist[i][k] + dist[k][j]);
                        }
                    }
                }
            }
        }
        ---
    b.Go实现
        ---
        func floyd(dist [][]int, n int) {
            for k := 0; k < n; k++ {
                for i := 0; i < n; i++ {
                    for j := 0; j < n; j++ {
                        if dist[i][k] != math.MaxInt32 && dist[k][j] != math.MaxInt32 {
                            dist[i][j] = min(dist[i][j], dist[i][k]+dist[k][j])
                        }
                    }
                }
            }
        }
        ---

03.Floyd应用
    a.传递闭包
        判断任意两点是否连通。
    b.最小环
        在更新dist[i][i]之前记录最小值。
    c.路径还原
        使用path数组记录中间节点。
        ---
        int[][] path;

        public List<Integer> getPath(int i, int j) {
            if (path[i][j] == -1) {
                return Arrays.asList(i, j);
            }
            List<Integer> result = new ArrayList<>();
            result.addAll(getPath(i, path[i][j]));
            result.addAll(getPath(path[i][j], j).subList(1, getPath(path[i][j], j).size()));
            return result;
        }
        ---

04.找到最终的安全状态
    a.问题描述
        LeetCode 802 - 找出所有安全节点。
    b.拓扑排序解法
        反向建图,从终点开始拓扑排序。

4.3 最小生成树扩展

01.次小生成树
    a.问题描述
        找出权值第二小的生成树。
    b.解法思路
        先求最小生成树,然后枚举删除MST中的每条边,加入非MST的边,求最小值。

02.最小生成树计数
    a.问题描述
        统计权值最小的生成树有多少棵。
    b.解法思路
        按权值分组,使用组合数学计算。

03.Kruskal重构树
    a.定义
        在Kruskal算法过程中,每次合并两个连通块时创建一个新节点。
    b.性质
        重构树是一棵二叉树,叶子节点是原图的节点,内部节点权值是合并时的边权。
    c.应用
        快速查询两点路径上的最大边权。

04.瓶颈生成树
    a.定义
        使得树上最大边权最小的生成树。
    b.性质
        最小生成树一定是瓶颈生成树。

4.4 SPFA求负环

01.负环检测
    a.基本思想
        如果一个节点入队次数超过n次,则存在负环。
    b.原理
        负环会导致无限松弛,节点会被反复加入队列。

02.SPFA判负环
    a.Java实现
        ---
        public boolean hasNegativeCycle(int n) {
            int[] dist = new int[n];
            int[] count = new int[n];  // 记录入队次数
            boolean[] inQueue = new boolean[n];
            Queue<Integer> queue = new LinkedList<>();

            // 将所有节点加入队列
            for (int i = 0; i < n; i++) {
                queue.offer(i);
                inQueue[i] = true;
            }

            while (!queue.isEmpty()) {
                int u = queue.poll();
                inQueue[u] = false;

                for (int i = h[u]; i != -1; i = ne[i]) {
                    int v = e[i];
                    if (dist[v] > dist[u] + w[i]) {
                        dist[v] = dist[u] + w[i];
                        count[v] = count[u] + 1;

                        if (count[v] >= n) {
                            return true;  // 存在负环
                        }

                        if (!inQueue[v]) {
                            queue.offer(v);
                            inQueue[v] = true;
                        }
                    }
                }
            }
            return false;
        }
        ---

03.AcWing 904虫洞
    a.题目
        判断是否存在负环,使得可以回到过去。
    b.SPFA解法
        建图后使用SPFA判断负环。

04.应用场景
    a.套利检测
        在货币兑换中检测是否存在套利机会。
    b.时间旅行
        判断是否可以回到过去。

4.5 差分约束

01.差分约束概述
    a.定义
        给定一组不等式x[i] - x[j] <= c[k],求一组解或判断无解。
    b.转化为图论
        将不等式转化为有向边,x[i] - x[j] <= c[k]对应边j->i权值为c[k]。
    c.求解方法
        使用SPFA求最短路,如果存在负环则无解。

02.差分约束系统
    a.标准形式
        x[i] - x[j] <= c[k]
    b.转化规则
        a.x[i] - x[j] <= c
            添加边j->i,权值c。
        b.x[i] - x[j] >= c
            转化为x[j] - x[i] <= -c,添加边i->j,权值-c。
        c.x[i] - x[j] < c
            转化为x[i] - x[j] <= c-1。
        d.x[i] - x[j] = c
            转化为x[i] - x[j] <= c且x[j] - x[i] <= -c。

03.求解实现
    a.Java实现
        ---
        public boolean solveDifferenceConstraints(int n, int[][] constraints) {
            // 建图
            for (int[] c : constraints) {
                int j = c[0], i = c[1], w = c[2];
                add(j, i, w);  // x[i] - x[j] <= w
            }

            // 添加超级源点
            for (int i = 1; i <= n; i++) {
                add(0, i, 0);
            }

            // SPFA求最短路
            return !hasNegativeCycle(n + 1);
        }
        ---

04.AcWing 1169糖果
    a.题目
        给定n个小朋友和m个约束条件,求最少需要多少糖果。
    b.差分约束解法
        将约束转化为不等式,建图后求最长路。

4.6 最近公共祖先

01.LCA概述
    a.定义
        树中两个节点的最近公共祖先是距离这两个节点最近的共同祖先节点。
    b.应用场景
        树上路径查询、树上距离计算。

02.倍增算法
    a.基本思想
        预处理每个节点向上跳2^k步的祖先,查询时二进制拆分。
    b.Java实现
        ---
        int[][] parent;  // parent[i][j]表示i向上跳2^j步的祖先
        int[] depth;
        int maxLog;

        public void preprocess(TreeNode root, int n) {
            maxLog = (int)(Math.log(n) / Math.log(2)) + 1;
            parent = new int[n][maxLog];
            depth = new int[n];
            dfs(root, -1, 0);

            for (int j = 1; j < maxLog; j++) {
                for (int i = 0; i < n; i++) {
                    if (parent[i][j-1] != -1) {
                        parent[i][j] = parent[parent[i][j-1]][j-1];
                    }
                }
            }
        }

        private void dfs(TreeNode node, int p, int d) {
            if (node == null) return;
            parent[node.val][0] = p;
            depth[node.val] = d;
            dfs(node.left, node.val, d + 1);
            dfs(node.right, node.val, d + 1);
        }

        public int lca(int u, int v) {
            if (depth[u] < depth[v]) {
                int temp = u; u = v; v = temp;
            }

            // 将u提升到与v同一深度
            int diff = depth[u] - depth[v];
            for (int i = 0; i < maxLog; i++) {
                if ((diff & (1 << i)) != 0) {
                    u = parent[u][i];
                }
            }

            if (u == v) return u;

            // 同时向上跳
            for (int i = maxLog - 1; i >= 0; i--) {
                if (parent[u][i] != parent[v][i]) {
                    u = parent[u][i];
                    v = parent[v][i];
                }
            }
            return parent[u][0];
        }
        ---

03.Tarjan算法
    a.基本思想
        离线算法,使用并查集和DFS一次性处理所有查询。
    b.时间复杂度
        O(n + q),其中q是查询次数。

4.7 强连通分量

01.强连通分量定义
    a.基本概念
        有向图中的极大强连通子图,子图内任意两点可达。
    b.缩点
        将每个强连通分量缩成一个点,得到DAG。

02.Tarjan算法
    a.基本思想
        使用DFS和栈,维护dfn和low数组。
    b.Java实现
        ---
        int[] dfn, low;
        boolean[] inStack;
        Stack<Integer> stack;
        List<List<Integer>> sccs;
        int timestamp, sccCount;

        public List<List<Integer>> tarjan(List<Integer>[] graph, int n) {
            dfn = new int[n];
            low = new int[n];
            inStack = new boolean[n];
            stack = new Stack<>();
            sccs = new ArrayList<>();
            timestamp = 0;
            sccCount = 0;

            for (int i = 0; i < n; i++) {
                if (dfn[i] == 0) {
                    dfs(i, graph);
                }
            }
            return sccs;
        }

        private void dfs(int u, List<Integer>[] graph) {
            dfn[u] = low[u] = ++timestamp;
            stack.push(u);
            inStack[u] = true;

            for (int v : graph[u]) {
                if (dfn[v] == 0) {
                    dfs(v, graph);
                    low[u] = Math.min(low[u], low[v]);
                } else if (inStack[v]) {
                    low[u] = Math.min(low[u], dfn[v]);
                }
            }

            if (dfn[u] == low[u]) {
                List<Integer> scc = new ArrayList<>();
                int v;
                do {
                    v = stack.pop();
                    inStack[v] = false;
                    scc.add(v);
                } while (v != u);
                sccs.add(scc);
                sccCount++;
            }
        }
        ---

03.Kosaraju算法
    a.基本思想
        两次DFS,第一次在原图上DFS得到后序遍历,第二次在反图上按后序逆序DFS。
    b.时间复杂度
        O(n + m)。

04.应用
    a.2-SAT问题
        判断布尔表达式是否可满足。
    b.有向图缩点
        将强连通分量缩成一个点。

4.8 双连通分量

01.双连通分量定义
    a.点双连通分量
        无向图中的极大子图,删除任意一个顶点后仍连通。
    b.边双连通分量
        无向图中的极大子图,删除任意一条边后仍连通。
    c.割点和桥
        割点:删除后图不连通的点。桥:删除后图不连通的边。

02.求割点
    a.Tarjan算法
        ---
        int[] dfn, low;
        boolean[] isCut;
        int timestamp;

        public List<Integer> findCutPoints(List<Integer>[] graph, int n) {
            dfn = new int[n];
            low = new int[n];
            isCut = new boolean[n];
            timestamp = 0;

            for (int i = 0; i < n; i++) {
                if (dfn[i] == 0) {
                    dfs(i, -1, graph);
                }
            }

            List<Integer> cutPoints = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                if (isCut[i]) cutPoints.add(i);
            }
            return cutPoints;
        }

        private void dfs(int u, int parent, List<Integer>[] graph) {
            dfn[u] = low[u] = ++timestamp;
            int children = 0;

            for (int v : graph[u]) {
                if (dfn[v] == 0) {
                    children++;
                    dfs(v, u, graph);
                    low[u] = Math.min(low[u], low[v]);

                    if (parent != -1 && low[v] >= dfn[u]) {
                        isCut[u] = true;
                    }
                } else if (v != parent) {
                    low[u] = Math.min(low[u], dfn[v]);
                }
            }

            if (parent == -1 && children > 1) {
                isCut[u] = true;
            }
        }
        ---

03.求桥
    a.Tarjan算法
        ---
        public List<int[]> findBridges(List<Integer>[] graph, int n) {
            dfn = new int[n];
            low = new int[n];
            timestamp = 0;
            List<int[]> bridges = new ArrayList<>();

            for (int i = 0; i < n; i++) {
                if (dfn[i] == 0) {
                    dfs(i, -1, graph, bridges);
                }
            }
            return bridges;
        }

        private void dfs(int u, int parent, List<Integer>[] graph, List<int[]> bridges) {
            dfn[u] = low[u] = ++timestamp;

            for (int v : graph[u]) {
                if (dfn[v] == 0) {
                    dfs(v, u, graph, bridges);
                    low[u] = Math.min(low[u], low[v]);

                    if (low[v] > dfn[u]) {
                        bridges.add(new int[]{u, v});
                    }
                } else if (v != parent) {
                    low[u] = Math.min(low[u], dfn[v]);
                }
            }
        }
        ---

5 高级数据结构

5.1 并查集进阶

01.带权并查集
    a.定义
        在并查集的基础上,维护节点到根节点的距离或关系。
    b.应用场景
        食物链问题、关系判断。

02.食物链问题
    a.问题描述
        有三种动物A、B、C,A吃B,B吃C,C吃A,给定若干关系,判断矛盾。
    b.带权并查集解法
        ---
        class UnionFind {
            int[] parent;
            int[] relation;  // 0:同类, 1:吃parent, 2:被parent吃

            public UnionFind(int n) {
                parent = new int[n];
                relation = new int[n];
                for (int i = 0; i < n; i++) {
                    parent[i] = i;
                }
            }

            public int find(int x) {
                if (parent[x] != x) {
                    int root = find(parent[x]);
                    relation[x] = (relation[x] + relation[parent[x]]) % 3;
                    parent[x] = root;
                }
                return parent[x];
            }

            public void union(int x, int y, int type) {
                int px = find(x);
                int py = find(y);
                if (px != py) {
                    parent[px] = py;
                    relation[px] = (relation[y] - relation[x] + type + 3) % 3;
                }
            }

            public boolean isSame(int x, int y) {
                if (find(x) != find(y)) return false;
                return relation[x] == relation[y];
            }
        }
        ---

03.按秩合并优化
    a.定义
        合并时将深度小的树合并到深度大的树上。
    b.实现
        ---
        class UnionFind {
            int[] parent;
            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 void union(int x, int y) {
                int px = find(x);
                int py = find(y);
                if (px == py) return;

                if (rank[px] < rank[py]) {
                    parent[px] = py;
                } else if (rank[px] > rank[py]) {
                    parent[py] = px;
                } else {
                    parent[py] = px;
                    rank[px]++;
                }
            }
        }
        ---

5.2 树状数组

01.树状数组概述
    a.定义
        树状数组Binary Indexed Tree是一种支持单点修改和区间查询的数据结构。
    b.时间复杂度
        单点修改O(log n),区间查询O(log n)。
    c.空间复杂度
        O(n)。

02.树状数组原理
    a.lowbit操作
        ---
        int lowbit(int x) {
            return x & -x;
        }
        ---
    b.区间和查询
        c[i]存储区间[i-lowbit(i)+1, i]的和。

03.树状数组实现
    a.Java实现
        ---
        class BIT {
            int[] tree;
            int n;

            public BIT(int n) {
                this.n = n;
                tree = new int[n + 1];
            }

            // 单点修改:给位置i加上val
            public void update(int i, int val) {
                while (i <= n) {
                    tree[i] += val;
                    i += lowbit(i);
                }
            }

            // 查询前缀和[1, i]
            public int query(int i) {
                int sum = 0;
                while (i > 0) {
                    sum += tree[i];
                    i -= lowbit(i);
                }
                return sum;
            }

            // 查询区间和[l, r]
            public int rangeQuery(int l, int r) {
                return query(r) - query(l - 1);
            }

            private int lowbit(int x) {
                return x & -x;
            }
        }
        ---
    b.Go实现
        ---
        type BIT struct {
            tree []int
            n    int
        }

        func NewBIT(n int) *BIT {
            return &BIT{
                tree: make([]int, n+1),
                n:    n,
            }
        }

        func (b *BIT) Update(i, val int) {
            for i <= b.n {
                b.tree[i] += val
                i += i & -i
            }
        }

        func (b *BIT) Query(i int) int {
            sum := 0
            for i > 0 {
                sum += b.tree[i]
                i -= i & -i
            }
            return sum
        }
        ---

04.应用场景
    a.区间和查询
        动态维护数组的区间和。
    b.逆序对统计
        统计数组中的逆序对数量。
    c.二维树状数组
        支持二维区间和查询和单点修改。

5.3 线段树

01.线段树概述
    a.定义
        线段树是一种二叉树结构,用于维护区间信息,支持区间查询和区间修改。
    b.时间复杂度
        建树O(n),单点修改O(log n),区间查询O(log n),区间修改O(log n)。
    c.空间复杂度
        O(4n)。

02.线段树基本操作
    a.Java实现
        ---
        class SegmentTree {
            int[] tree, lazy;
            int n;

            public SegmentTree(int[] arr) {
                n = arr.length;
                tree = new int[4 * n];
                lazy = new int[4 * n];
                build(arr, 1, 0, n - 1);
            }

            // 建树
            private void build(int[] arr, int node, int start, int end) {
                if (start == end) {
                    tree[node] = arr[start];
                    return;
                }
                int mid = (start + end) / 2;
                build(arr, 2 * node, start, mid);
                build(arr, 2 * node + 1, mid + 1, end);
                tree[node] = tree[2 * node] + tree[2 * node + 1];
            }

            // 区间查询
            public int query(int l, int r) {
                return query(1, 0, n - 1, l, r);
            }

            private int query(int node, int start, int end, int l, int r) {
                if (r < start || end < l) return 0;
                if (l <= start && end <= r) return tree[node];

                pushDown(node, start, end);
                int mid = (start + end) / 2;
                return query(2 * node, start, mid, l, r) +
                       query(2 * node + 1, mid + 1, end, l, r);
            }

            // 区间修改
            public void update(int l, int r, int val) {
                update(1, 0, n - 1, l, r, val);
            }

            private void update(int node, int start, int end, int l, int r, int val) {
                if (r < start || end < l) return;
                if (l <= start && end <= r) {
                    tree[node] += (end - start + 1) * val;
                    lazy[node] += val;
                    return;
                }

                pushDown(node, start, end);
                int mid = (start + end) / 2;
                update(2 * node, start, mid, l, r, val);
                update(2 * node + 1, mid + 1, end, l, r, val);
                tree[node] = tree[2 * node] + tree[2 * node + 1];
            }

            // 懒标记下传
            private void pushDown(int node, int start, int end) {
                if (lazy[node] != 0) {
                    int mid = (start + end) / 2;
                    tree[2 * node] += (mid - start + 1) * lazy[node];
                    tree[2 * node + 1] += (end - mid) * lazy[node];
                    lazy[2 * node] += lazy[node];
                    lazy[2 * node + 1] += lazy[node];
                    lazy[node] = 0;
                }
            }
        }
        ---

03.线段树应用
    a.区间最值查询
        维护区间最大值或最小值。
    b.区间GCD
        维护区间最大公约数。
    c.区间修改
        支持区间加、区间乘等操作。

5.4 可持久化数据结构

01.可持久化概述
    a.定义
        保留数据结构的所有历史版本,支持查询任意版本。
    b.应用场景
        版本控制、时间旅行查询。

02.可持久化线段树
    a.基本思想
        每次修改只创建O(log n)个新节点,共享未修改的节点。
    b.主席树
        可持久化线段树的别称,用于区间第K小问题。

03.可持久化并查集
    a.实现方式
        使用可持久化数组存储parent数组。
    b.应用
        支持回退操作的并查集。

5.5 平衡树Treap

01.Treap概述
    a.定义
        树堆,结合二叉搜索树和堆的性质。
    b.特点
        按key满足BST性质,按priority满足堆性质。

02.Treap操作
    a.旋转
        左旋和右旋维护堆性质。
    b.插入
        按BST插入,然后旋转维护堆性质。
    c.删除
        旋转到叶子节点后删除。

03.应用
    a.动态维护有序集合
        支持插入、删除、查询第K大。
    b.区间操作
        支持区间翻转、区间求和。

5.6 AC自动机

01.AC自动机概述
    a.定义
        在Trie树基础上加上fail指针,实现多模式串匹配。
    b.应用场景
        敏感词过滤、病毒检测。

02.构建AC自动机
    a.建立Trie树
        插入所有模式串。
    b.构建fail指针
        使用BFS构建fail指针。

03.匹配过程
    a.沿着Trie树匹配
        失配时跳转到fail指针。
    b.统计匹配
        记录所有匹配的模式串。

5.7 数据结构综合应用

01.LRU缓存
    a.实现
        HashMap + 双向链表。
    b.时间复杂度
        get和put都是O(1)。

02.LFU缓存
    a.实现
        HashMap + 频率链表。
    b.淘汰策略
        淘汰访问频率最低的,频率相同淘汰最久未使用的。

03.跳表
    a.定义
        多层链表,支持O(log n)查找。
    b.应用
        Redis的有序集合实现。

04.布隆过滤器
    a.原理
        使用多个哈希函数和位数组。
    b.特点
        可能误判存在,但不会误判不存在。

6 数学进阶

6.1 筛质数

01.埃氏筛
    a.基本思想
        从2开始,筛掉所有2的倍数,再筛3的倍数,依此类推。
    b.时间复杂度
        O(n log log n)。

02.线性筛
    a.基本思想
        每个合数只被其最小质因子筛掉一次。
    b.Java实现
        ---
        public List<Integer> linearSieve(int n) {
            boolean[] isPrime = new boolean[n + 1];
            Arrays.fill(isPrime, true);
            List<Integer> primes = new ArrayList<>();

            for (int i = 2; i <= n; i++) {
                if (isPrime[i]) primes.add(i);
                for (int j = 0; j < primes.size() && i * primes.get(j) <= n; j++) {
                    isPrime[i * primes.get(j)] = false;
                    if (i % primes.get(j) == 0) break;
                }
            }
            return primes;
        }
        ---

6.2 欧拉函数

01.欧拉函数定义
    a.φ(n)
        小于等于n且与n互质的正整数个数。
    b.性质
        若n = p^k,则φ(n) = p^(k-1) * (p-1)。

02.欧拉函数计算
    a.单个数
        ---
        public int phi(int n) {
            int res = n;
            for (int i = 2; i <= n / i; i++) {
                if (n % i == 0) {
                    res = res / i * (i - 1);
                    while (n % i == 0) n /= i;
                }
            }
            if (n > 1) res = res / n * (n - 1);
            return res;
        }
        ---
    b.筛法求1到n的欧拉函数
        线性筛的同时计算欧拉函数。

03.欧拉定理
    a.定理
        若gcd(a,n)=1,则a^φ(n) ≡ 1 (mod n)。
    b.应用
        求逆元、快速幂。

6.3 同余理论

01.同余基本性质
    a.定义
        a ≡ b (mod m)表示a和b除以m的余数相同。
    b.运算性质
        加减乘满足同余,除法需要逆元。

02.逆元
    a.定义
        若ax ≡ 1 (mod m),则x是a模m的逆元。
    b.求法
        a.扩展欧几里得
            求解ax + my = 1。
        b.费马小定理
            当m是质数时,a^(m-2)是a的逆元。

03.中国剩余定理
    a.定理
        求解同余方程组x ≡ a_i (mod m_i),其中m_i两两互质。
    b.应用
        大数取模、日期计算。

6.4 矩阵乘法

01.矩阵乘法基础
    a.定义
        C[i][j] = Σ A[i][k] * B[k][j]
    b.时间复杂度
        O(n³)。

02.矩阵快速幂
    a.应用
        加速线性递推,如斐波那契数列。
    b.Java实现
        ---
        public long[][] matrixPow(long[][] A, long n, long mod) {
            int size = A.length;
            long[][] res = new long[size][size];
            for (int i = 0; i < size; i++) res[i][i] = 1;

            while (n > 0) {
                if ((n & 1) == 1) res = multiply(res, A, mod);
                A = multiply(A, A, mod);
                n >>= 1;
            }
            return res;
        }

        private long[][] multiply(long[][] A, long[][] B, long mod) {
            int n = A.length;
            long[][] C = new long[n][n];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    for (int k = 0; k < n; k++) {
                        C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % mod;
                    }
                }
            }
            return C;
        }
        ---

03.应用
    a.斐波那契数列
        F(n) = [[1,1],[1,0]]^n
    b.图的路径计数
        A^k[i][j]表示i到j长度为k的路径数。

6.5 组合计数进阶

01.Lucas定理
    a.定理
        C(n,m) mod p = C(n/p, m/p) * C(n%p, m%p) mod p
    b.应用
        大组合数取模。

02.卡特兰数
    a.定义
        Cat(n) = C(2n,n) / (n+1)
    b.应用
        括号匹配、出栈序列、二叉树计数。

03.容斥原理
    a.基本形式
        |A∪B| = |A| + |B| - |A∩B|
    b.一般形式
        |A₁∪A₂∪...∪Aₙ| = Σ|Aᵢ| - Σ|Aᵢ∩Aⱼ| + ...

6.6 高斯消元

01.高斯消元法
    a.基本思想
        通过初等行变换将增广矩阵化为阶梯形。
    b.步骤
        a.消元
            将矩阵化为上三角形式。
        b.回代
            从最后一行开始求解。

02.Java实现
    a.代码
        ---
        public double[] gaussElimination(double[][] A, double[] b) {
            int n = A.length;
            for (int i = 0; i < n; i++) {
                // 选主元
                int maxRow = i;
                for (int k = i + 1; k < n; k++) {
                    if (Math.abs(A[k][i]) > Math.abs(A[maxRow][i])) {
                        maxRow = k;
                    }
                }
                swap(A, i, maxRow);
                double temp = b[i];
                b[i] = b[maxRow];
                b[maxRow] = temp;

                // 消元
                for (int k = i + 1; k < n; k++) {
                    double factor = A[k][i] / A[i][i];
                    for (int j = i; j < n; j++) {
                        A[k][j] -= factor * A[i][j];
                    }
                    b[k] -= factor * b[i];
                }
            }

            // 回代
            double[] x = new double[n];
            for (int i = n - 1; i >= 0; i--) {
                x[i] = b[i];
                for (int j = i + 1; j < n; j++) {
                    x[i] -= A[i][j] * x[j];
                }
                x[i] /= A[i][i];
            }
            return x;
        }
        ---

03.应用
    a.求解线性方程组
        Ax = b
    b.求逆矩阵
        [A|I] -> [I|A⁻¹]

6.7 容斥原理

01.容斥原理
    a.二集合
        |A∪B| = |A| + |B| - |A∩B|
    b.三集合
        |A∪B∪C| = |A| + |B| + |C| - |A∩B| - |A∩C| - |B∩C| + |A∩B∩C|
    c.一般形式
        奇加偶减。

02.应用
    a.不定方程非负整数解
        x₁+x₂+...+xₙ = m,0 ≤ xᵢ ≤ aᵢ
    b.错排问题
        D(n) = n! * (1 - 1/1! + 1/2! - 1/3! + ... + (-1)ⁿ/n!)

03.莫比乌斯反演
    a.莫比乌斯函数
        μ(n) = 1 (n=1), 0 (n有平方因子), (-1)^k (n是k个不同质数的乘积)
    b.反演公式
        f(n) = Σ g(d) ⟺ g(n) = Σ μ(d) * f(n/d)

6.8 博弈论

01.Nim游戏
    a.规则
        n堆石子,每次取任意堆的任意个,取走最后一个石子的人获胜。
    b.必胜策略
        所有堆石子数异或和为0时先手必败,否则先手必胜。

02.SG函数
    a.定义
        SG(x) = mex{SG(y) | y是x的后继状态}
    b.SG定理
        多个独立游戏的SG值等于各游戏SG值的异或和。

03.博弈树
    a.极大极小搜索
        交替选择对自己最有利的策略。
    b.Alpha-Beta剪枝
        剪掉不可能影响结果的分支。

04.经典博弈
    a.巴什博弈
        n个石子,每次取1到m个。
    b.威佐夫博弈
        两堆石子,可以从一堆取任意个或从两堆取相同个数。

7 综合实战

7.1 DP综合题目

01.打家劫舍系列
    a.打家劫舍I
        LeetCode 198 - 线性DP,不能偷相邻的房子。
        ---
        public int rob(int[] nums) {
            if (nums.length == 0) return 0;
            if (nums.length == 1) return nums[0];

            int prev2 = 0, prev1 = 0;
            for (int num : nums) {
                int curr = Math.max(prev1, prev2 + num);
                prev2 = prev1;
                prev1 = curr;
            }
            return prev1;
        }
        ---
    b.打家劫舍II
        LeetCode 213 - 环形数组,分两种情况讨论。
    c.打家劫舍III
        LeetCode 337 - 树形DP,在二叉树上偷窃。

02.股票买卖系列
    a.买卖股票的最佳时机
        LeetCode 121 - 只能交易一次。
    b.买卖股票的最佳时机II
        LeetCode 122 - 可以多次交易。
    c.买卖股票的最佳时机III
        LeetCode 123 - 最多两次交易。
    d.买卖股票的最佳时机IV
        LeetCode 188 - 最多k次交易。
        ---
        public int maxProfit(int k, int[] prices) {
            if (prices.length == 0) return 0;

            int n = prices.length;
            k = Math.min(k, n / 2);
            int[][] buy = new int[n][k + 1];
            int[][] sell = new int[n][k + 1];

            for (int i = 0; i <= k; i++) {
                buy[0][i] = -prices[0];
            }

            for (int i = 1; i < n; i++) {
                for (int j = 1; j <= k; j++) {
                    buy[i][j] = Math.max(buy[i-1][j], sell[i-1][j-1] - prices[i]);
                    sell[i][j] = Math.max(sell[i-1][j], buy[i-1][j] + prices[i]);
                }
            }

            return sell[n-1][k];
        }
        ---

03.子序列问题
    a.最长递增子序列
        LeetCode 300 - O(n log n)解法。
    b.俄罗斯套娃信封问题
        LeetCode 354 - 二维LIS问题。
    c.最长公共子序列
        LeetCode 1143 - 经典线性DP。

7.2 搜索综合题目

01.八数码问题
    a.问题描述
        3×3网格,8个数字和1个空格,移动空格使数字有序排列。
    b.解法
        BFS、A*、IDA*。

02.数独求解
    a.问题描述
        填充9×9数独,使每行、每列、每宫数字1-9各出现一次。
    b.解法
        DFS + 剪枝。

03.24点游戏
    a.问题描述
        4个数字通过加减乘除得到24。
    b.解法
        DFS枚举所有可能的运算顺序和运算符。

04.骑士巡游
    a.问题描述
        国际象棋棋盘上,骑士遍历所有格子恰好一次。
    b.解法
        DFS + 启发式搜索。

7.3 图论综合题目

01.最短路综合
    a.单源最短路
        Dijkstra、SPFA、Bellman-Ford选择。
    b.多源最短路
        Floyd算法。
    c.次短路
        维护最短路和次短路两个距离。

02.最小生成树综合
    a.Kruskal算法
        边排序 + 并查集。
    b.Prim算法
        贪心选择最小边。
    c.次小生成树
        枚举删除MST的边。

03.网络流综合
    a.最大流
        Dinic、ISAP算法。
    b.最小割
        最大流最小割定理。
    c.费用流
        最小费用最大流。

04.强连通分量
    a.Tarjan算法
        一次DFS求所有SCC。
    b.Kosaraju算法
        两次DFS求SCC。
    c.缩点
        将SCC缩成一个点得到DAG。

7.4 数据结构综合题目

01.树状数组应用
    a.单点修改区间查询
        标准树状数组。
    b.区间修改单点查询
        差分 + 树状数组。
    c.区间修改区间查询
        两个树状数组。

02.线段树应用
    a.区间和查询
        支持区间修改和区间查询。
    b.区间最值
        维护区间最大值或最小值。
    c.区间GCD
        利用GCD的性质。

03.并查集应用
    a.连通性判断
        判断两点是否连通。
    b.带权并查集
        维护节点间的关系。
    c.可撤销并查集
        支持回退操作。

04.平衡树应用
    a.动态第K大
        支持插入删除和查询第K大。
    b.区间翻转
        文艺平衡树。
    c.区间操作
        支持区间加、区间求和等。

7.5 竞赛策略

01.竞赛准备
    a.知识储备
        掌握所有基础和进阶算法,准备代码模板。
    b.刷题训练
        每天保持刷题,熟悉各种题型。
    c.模拟比赛
        定期参加模拟比赛,提升实战能力。
    d.总结复盘
        比赛后总结错误和不足。

02.比赛策略
    a.快速读题
        先浏览所有题目,选择擅长的题目先做。
    b.时间分配
        简单题快速通过,难题合理分配时间。
    c.代码调试
        使用样例测试,注意边界条件。
    d.提交策略
        确保代码正确后再提交,避免罚时。

03.常见错误
    a.数组越界
        注意数组下标范围。
    b.整数溢出
        使用long类型或取模。
    c.边界条件
        特殊情况如空数组、单元素等。
    d.算法选择
        根据数据范围选择合适的算法。

04.提升技巧
    a.代码模板
        整理常用算法的代码模板,比赛时快速调用。
    b.快速输入输出
        使用BufferedReader和PrintWriter提升IO速度。
    c.调试技巧
        使用assert语句、打印中间结果。
    d.心态调整
        保持冷静,遇到难题不要慌张。

05.学习资源
    a.在线平台
        Codeforces、AtCoder、LeetCode周赛。
    b.算法书籍
        算法竞赛进阶指南、挑战程序设计竞赛。
    c.题库
        AcWing、洛谷、牛客网。