1 介绍

1.1 基础算法概述

01.什么是算法
    a.定义
        算法是解决特定问题的有限步骤序列,具有输入、输出、确定性、有限性和可行性五个特征。
    b.算法的重要性
        a.提升编程能力
            掌握算法能够帮助开发者编写更高效、更优雅的代码。
        b.解决实际问题
            算法是解决复杂问题的核心工具,如搜索、排序、优化等。
        c.面试必备
            几乎所有技术面试都会考察算法能力。

02.基础算法分类
    a.排序算法
        快速排序、归并排序、堆排序、计数排序等。
    b.查找算法
        二分查找、哈希查找、树查找等。
    c.双指针算法
        对撞指针、快慢指针、滑动窗口等。
    d.贪心算法
        局部最优解推导全局最优解。
    e.动态规划
        通过子问题的最优解构造原问题的最优解。
    f.搜索算法
        深度优先搜索DFS、广度优先搜索BFS。
    g.图论算法
        最短路、最小生成树、拓扑排序等。

03.AcWing基础算法体系
    a.基础算法
        排序、二分、高精度、前缀和差分、双指针、位运算、离散化、区间合并。
    b.数据结构
        链表、栈队列、KMP、Trie、并查集、堆、哈希表。
    c.搜索与图论
        DFS、BFS、树与图的遍历、最短路、最小生成树、二分图。
    d.数学知识
        质数、约数、欧拉函数、快速幂、组合计数、容斥原理、博弈论。
    e.动态规划
        背包问题、线性DP、区间DP、状态压缩DP、树形DP。

04.学习建议
    a.理论与实践结合
        先理解算法原理,再动手实现代码,最后通过题目巩固。
    b.循序渐进
        从简单到复杂,从单一算法到综合应用。
    c.多做题目
        刷题是提升算法能力的最有效方法,建议每天2-3题。
    d.总结归纳
        定期总结常见题型和解题模板,形成自己的知识体系。

1.2 算法复杂度分析

01.时间复杂度
    a.常见时间复杂度
        a.O(1)常数时间
            数组随机访问、哈希表查找、栈队列操作。
        b.O(log n)对数时间
            二分查找、平衡树操作、堆操作。
        c.O(n)线性时间
            数组遍历、链表遍历、线性查找。
        d.O(n log n)线性对数时间
            快速排序、归并排序、堆排序的平均时间复杂度。
        e.O(n²)平方时间
            冒泡排序、选择排序、插入排序。
        f.O(2ⁿ)指数时间
            递归求解所有子集、暴力搜索。
    b.时间复杂度比较
        O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(n³) < O(2ⁿ) < O(n!)

02.空间复杂度
    a.常见空间复杂度
        a.O(1)常数空间
            只使用固定数量的变量,如原地排序。
        b.O(n)线性空间
            需要额外的数组或列表,如归并排序。
        c.O(log n)对数空间
            递归调用栈的深度,如二分查找递归实现。
        d.O(n²)平方空间
            二维数组,如动态规划的状态数组。

03.复杂度分析方法
    a.数据规模估算
        a.n ≤ 10
            可以使用O(n!)的算法,如全排列。
        b.n ≤ 20
            可以使用O(2ⁿ)的算法,如状态压缩DP。
        c.n ≤ 100
            可以使用O(n³)的算法,如Floyd算法。
        d.n ≤ 1000
            可以使用O(n²)的算法,如冒泡排序。
        e.n ≤ 10⁵
            可以使用O(n log n)的算法,如快速排序。
        f.n ≤ 10⁶
            可以使用O(n)的算法,如线性扫描。
        g.n ≤ 10⁹
            只能使用O(log n)或O(1)的算法,如二分查找。
    b.时间限制估算
        一般OJ系统1秒可以执行10⁸次基本操作。

04.优化策略
    a.时间换空间
        使用额外的空间存储中间结果,减少重复计算。
    b.空间换时间
        使用哈希表、前缀和等技术,用空间换取时间。
    c.算法优化
        选择更优的算法,如用快排替代冒泡排序。
    d.常数优化
        减少不必要的操作,如循环展开、位运算等。

1.3 学习路线图

01.第一阶段:基础算法
    a.排序算法
        a.学习内容
            快速排序、归并排序、堆排序的原理和实现。
        b.学习时间
            1-2周,每种排序都要手写实现。
        c.练习题目
            AcWing 785快速排序、786归并排序、787堆排序。
    b.二分查找
        a.学习内容
            整数二分、浮点数二分、二分答案。
        b.学习时间
            3-5天,掌握二分的本质和边界处理。
        c.练习题目
            AcWing 789数的范围、790数的三次方根。

02.第二阶段:双指针与区间
    a.双指针算法
        a.学习内容
            对撞指针、快慢指针、滑动窗口。
        b.学习时间
            1周,理解双指针的应用场景。
        c.练习题目
            AcWing 799最长连续不重复子序列。
    b.前缀和与差分
        a.学习内容
            一维前缀和、二维前缀和、差分数组。
        b.学习时间
            1周,掌握前缀和的优化技巧。
        c.练习题目
            AcWing 795前缀和、796子矩阵的和。

03.第三阶段:基础数据结构
    a.链表与栈队列
        a.学习内容
            单链表、双链表、栈、队列、单调栈、单调队列。
        b.学习时间
            2周,理解数据结构的应用。
        c.练习题目
            AcWing 826单链表、830单调栈、154滑动窗口。
    b.并查集与堆
        a.学习内容
            并查集的路径压缩、堆的上浮下沉操作。
        b.学习时间
            1周,掌握高级数据结构。
        c.练习题目
            AcWing 836合并集合、838堆排序。

04.第四阶段:搜索与图论
    a.DFS与BFS
        a.学习内容
            深度优先搜索、广度优先搜索、剪枝优化。
        b.学习时间
            2周,理解搜索的本质。
        c.练习题目
            AcWing 842排列数字、844走迷宫。
    b.最短路与最小生成树
        a.学习内容
            Dijkstra、Bellman-Ford、Floyd、Prim、Kruskal算法。
        b.学习时间
            2-3周,掌握图论基础算法。
        c.练习题目
            AcWing 849Dijkstra求最短路、858Prim算法求最小生成树。

05.第五阶段:动态规划
    a.背包问题
        a.学习内容
            01背包、完全背包、多重背包、分组背包。
        b.学习时间
            2-3周,理解DP的状态转移。
        c.练习题目
            AcWing 2 01背包问题、3完全背包问题。
    b.线性DP
        a.学习内容
            最长上升子序列、最长公共子序列、编辑距离。
        b.学习时间
            2周,掌握线性DP的套路。
        c.练习题目
            AcWing 895最长上升子序列、897最长公共子序列。

1.4 AcWing基础算法体系

01.AcWing算法基础课
    a.课程特点
        a.系统完整
            覆盖所有基础算法和数据结构。
        b.由浅入深
            从简单到复杂,循序渐进。
        c.配套练习
            每个知识点都有对应的练习题。
    b.课程内容
        基础算法、数据结构、搜索与图论、数学知识、动态规划。

02.基础算法模板
    a.快速排序模板
        ---
        void quickSort(int[] arr, int l, int r) {
            if (l >= r) return;
            int i = l - 1, j = r + 1, x = arr[l + r >> 1];
            while (i < j) {
                do i++; while (arr[i] < x);
                do j--; while (arr[j] > x);
                if (i < j) {
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
            quickSort(arr, l, j);
            quickSort(arr, j + 1, r);
        }
        ---
    b.归并排序模板
        ---
        void mergeSort(int[] arr, int l, int r) {
            if (l >= r) return;
            int mid = l + r >> 1;
            mergeSort(arr, l, mid);
            mergeSort(arr, mid + 1, r);

            int[] temp = new int[r - l + 1];
            int i = l, j = mid + 1, k = 0;
            while (i <= mid && j <= r) {
                if (arr[i] <= arr[j]) temp[k++] = arr[i++];
                else temp[k++] = arr[j++];
            }
            while (i <= mid) temp[k++] = arr[i++];
            while (j <= r) temp[k++] = arr[j++];

            for (i = l, k = 0; i <= r; i++, k++) {
                arr[i] = temp[k];
            }
        }
        ---
    c.二分查找模板
        ---
        // 查找左边界
        int binarySearchLeft(int[] arr, int target) {
            int l = 0, r = arr.length - 1;
            while (l < r) {
                int mid = l + r >> 1;
                if (arr[mid] >= target) r = mid;
                else l = mid + 1;
            }
            return l;
        }

        // 查找右边界
        int binarySearchRight(int[] arr, int target) {
            int l = 0, r = arr.length - 1;
            while (l < r) {
                int mid = l + r + 1 >> 1;
                if (arr[mid] <= target) l = mid;
                else r = mid - 1;
            }
            return l;
        }
        ---

03.刷题顺序
    a.基础算法
        先学习排序、二分、双指针、前缀和等基础算法。
    b.数据结构
        掌握链表、栈队列、并查集、堆、哈希表等数据结构。
    c.搜索与图论
        学习DFS、BFS、最短路、最小生成树等算法。
    d.动态规划
        从背包问题开始,逐步掌握各种DP类型。

04.学习资源
    a.AcWing官网
        提供完整的算法课程和题库。
    b.LeetCode
        大量的算法题目,适合刷题练习。
    c.算法书籍
        算法导论、算法第四版、算法竞赛入门经典等。

1.5 刷题策略

01.刷题平台选择
    a.AcWing
        a.特点
            题目质量高,有详细的题解和讨论区。
        b.适合人群
            算法初学者,系统学习算法的人。
        c.推荐课程
            算法基础课、算法提高课、算法进阶课。
    b.LeetCode
        a.特点
            题目数量多,覆盖面广,有中英文版本。
        b.适合人群
            准备面试的人,想要提升算法能力的人。
        c.推荐题单
            LeetCode Hot 100、剑指Offer、面试经典150题。
    c.牛客网
        a.特点
            有大量的笔试题和面试题。
        b.适合人群
            准备校招和社招的人。

02.刷题方法
    a.按标签刷题
        a.优点
            集中练习同一类型的题目,容易形成解题思路。
        b.推荐标签
            数组、链表、树、动态规划、贪心、回溯等。
    b.按难度刷题
        a.初学者
            从简单题开始,逐步提升难度。
        b.进阶者
            重点刷中等题,偶尔挑战困难题。
    c.按公司刷题
        a.目标明确
            针对目标公司的高频题进行练习。
        b.效率高
            能够快速提升面试通过率。

03.刷题技巧
    a.先思考再编码
        不要急于写代码,先在纸上画图分析。
    b.多种解法
        同一道题尝试多种解法,比较时间空间复杂度。
    c.总结归纳
        定期总结常见题型和解题模板。
    d.复习巩固
        隔一段时间回顾之前做过的题目。

04.时间安排
    a.每日刷题
        a.初学者
            每天1-2题,重点理解算法原理。
        b.进阶者
            每天2-3题,提升解题速度。
        c.冲刺期
            每天3-5题,保持手感。
    b.周末复习
        周末花2-3小时复习本周做过的题目。
    c.月度总结
        每月总结常见题型和解题技巧。

05.常见误区
    a.只看不做
        光看题解不动手实现,无法真正掌握算法。
    b.追求数量
        盲目刷题不总结,做再多题也没用。
    c.畏难情绪
        遇到困难题就放弃,要学会分解问题。
    d.忽视基础
        基础不牢,地动山摇,要打好基础再进阶。

2 排序与查找

2.1 排序算法概述

01.排序算法分类
    a.比较排序
        基于元素比较的排序算法,时间复杂度下界为O(n log n)。
    b.非比较排序
        不基于比较的排序算法,如计数排序、桶排序、基数排序。
    c.稳定排序
        相等元素的相对位置不变,如归并排序、插入排序。
    d.不稳定排序
        相等元素的相对位置可能改变,如快速排序、堆排序。

02.常见排序算法对比
    a.冒泡排序
        时间O(n²),空间O(1),稳定。
    b.选择排序
        时间O(n²),空间O(1),不稳定。
    c.插入排序
        时间O(n²),空间O(1),稳定。
    d.快速排序
        时间O(n log n)平均,空间O(log n),不稳定。
    e.归并排序
        时间O(n log n),空间O(n),稳定。
    f.堆排序
        时间O(n log n),空间O(1),不稳定。

03.排序算法选择
    a.数据量小
        插入排序,实现简单,常数小。
    b.数据量大
        快速排序或归并排序,效率高。
    c.需要稳定性
        归并排序,保证稳定性。
    d.空间受限
        堆排序,原地排序。

2.2 快速排序

01.快速排序原理
    a.分治思想
        选择基准元素,将数组分为小于基准和大于基准两部分,递归排序。
    b.时间复杂度
        平均O(n log n),最坏O(n²)。
    c.空间复杂度
        O(log n)递归栈空间。

02.AcWing快排模板
    a.Java实现
        ---
        public void quickSort(int[] arr, int l, int r) {
            if (l >= r) return;

            int i = l - 1, j = r + 1, x = arr[l + r >> 1];
            while (i < j) {
                do i++; while (arr[i] < x);
                do j--; while (arr[j] > x);
                if (i < j) {
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }

            quickSort(arr, l, j);
            quickSort(arr, j + 1, r);
        }
        ---
    b.Go实现
        ---
        func quickSort(arr []int, l, r int) {
            if l >= r {
                return
            }

            i, j, x := l-1, r+1, arr[(l+r)>>1]
            for i < j {
                for {
                    i++
                    if arr[i] >= x {
                        break
                    }
                }
                for {
                    j--
                    if arr[j] <= x {
                        break
                    }
                }
                if i < j {
                    arr[i], arr[j] = arr[j], arr[i]
                }
            }

            quickSort(arr, l, j)
            quickSort(arr, j+1, r)
        }
        ---

03.快排优化
    a.三数取中
        选择左、中、右三个元素的中位数作为基准,避免最坏情况。
    b.小数组优化
        当子数组长度小于阈值时,使用插入排序。
    c.三路快排
        将数组分为小于、等于、大于基准三部分,处理重复元素更高效。

04.AcWing 785快速排序
    a.题目
        给定n个整数,请用快速排序算法将其从小到大排序。
    b.输入格式
        第一行包含整数n,第二行包含n个整数。
    c.输出格式
        输出排序后的数组。

2.3 归并排序

01.归并排序原理
    a.分治思想
        将数组分成两半,递归排序,然后合并两个有序数组。
    b.时间复杂度
        O(n log n),稳定的时间复杂度。
    c.空间复杂度
        O(n),需要额外数组存储临时结果。

02.AcWing归并模板
    a.Java实现
        ---
        public void mergeSort(int[] arr, int l, int r) {
            if (l >= r) return;

            int mid = l + r >> 1;
            mergeSort(arr, l, mid);
            mergeSort(arr, mid + 1, r);

            int[] temp = new int[r - l + 1];
            int i = l, j = mid + 1, k = 0;

            while (i <= mid && j <= r) {
                if (arr[i] <= arr[j]) temp[k++] = arr[i++];
                else temp[k++] = arr[j++];
            }

            while (i <= mid) temp[k++] = arr[i++];
            while (j <= r) temp[k++] = arr[j++];

            for (i = l, k = 0; i <= r; i++, k++) {
                arr[i] = temp[k];
            }
        }
        ---
    b.Go实现
        ---
        func mergeSort(arr []int, l, r int) {
            if l >= r {
                return
            }

            mid := (l + r) >> 1
            mergeSort(arr, l, mid)
            mergeSort(arr, mid+1, r)

            temp := make([]int, r-l+1)
            i, j, k := l, mid+1, 0

            for i <= mid && j <= r {
                if arr[i] <= arr[j] {
                    temp[k] = arr[i]
                    i++
                } else {
                    temp[k] = arr[j]
                    j++
                }
                k++
            }

            for i <= mid {
                temp[k] = arr[i]
                i++
                k++
            }
            for j <= r {
                temp[k] = arr[j]
                j++
                k++
            }

            for i, k = l, 0; i <= r; i, k = i+1, k+1 {
                arr[i] = temp[k]
            }
        }
        ---

03.逆序对问题
    a.问题描述
        AcWing 788逆序对的数量 - 统计数组中逆序对的个数。
    b.归并排序解法
        ---
        long count = 0;

        public long mergeSort(int[] arr, int l, int r) {
            if (l >= r) return 0;

            int mid = l + r >> 1;
            long res = mergeSort(arr, l, mid) + mergeSort(arr, mid + 1, r);

            int[] temp = new int[r - l + 1];
            int i = l, j = mid + 1, k = 0;

            while (i <= mid && j <= r) {
                if (arr[i] <= arr[j]) {
                    temp[k++] = arr[i++];
                } else {
                    temp[k++] = arr[j++];
                    res += mid - i + 1;  // 统计逆序对
                }
            }

            while (i <= mid) temp[k++] = arr[i++];
            while (j <= r) temp[k++] = arr[j++];

            for (i = l, k = 0; i <= r; i++, k++) {
                arr[i] = temp[k];
            }

            return res;
        }
        ---

2.4 堆排序

01.堆排序原理
    a.基本思想
        利用堆这种数据结构进行排序,先建立最大堆,然后依次取出堆顶元素。
    b.时间复杂度
        O(n log n),稳定的时间复杂度。
    c.空间复杂度
        O(1),原地排序。

02.堆的基本操作
    a.下沉操作
        ---
        void siftDown(int[] arr, int i, int n) {
            while (2 * i + 1 < n) {
                int left = 2 * i + 1;
                int right = 2 * i + 2;
                int largest = i;

                if (left < n && arr[left] > arr[largest]) {
                    largest = left;
                }
                if (right < n && arr[right] > arr[largest]) {
                    largest = right;
                }

                if (largest == i) break;

                int temp = arr[i];
                arr[i] = arr[largest];
                arr[largest] = temp;
                i = largest;
            }
        }
        ---

03.堆排序实现
    a.Java实现
        ---
        public void heapSort(int[] arr) {
            int n = arr.length;

            // 建堆:从最后一个非叶子节点开始下沉
            for (int i = n / 2 - 1; i >= 0; i--) {
                siftDown(arr, i, n);
            }

            // 排序:依次取出堆顶元素
            for (int i = n - 1; i > 0; i--) {
                // 交换堆顶和末尾元素
                int temp = arr[0];
                arr[0] = arr[i];
                arr[i] = temp;

                // 对剩余元素进行下沉
                siftDown(arr, 0, i);
            }
        }
        ---
    b.Go实现
        ---
        func heapSort(arr []int) {
            n := len(arr)

            // 建堆
            for i := n/2 - 1; i >= 0; i-- {
                siftDown(arr, i, n)
            }

            // 排序
            for i := n - 1; i > 0; i-- {
                arr[0], arr[i] = arr[i], arr[0]
                siftDown(arr, 0, i)
            }
        }

        func siftDown(arr []int, i, n int) {
            for 2*i+1 < n {
                left := 2*i + 1
                right := 2*i + 2
                largest := i

                if left < n && arr[left] > arr[largest] {
                    largest = left
                }
                if right < n && arr[right] > arr[largest] {
                    largest = right
                }

                if largest == i {
                    break
                }

                arr[i], arr[largest] = arr[largest], arr[i]
                i = largest
            }
        }
        ---

04.AcWing 838堆排序
    a.题目
        给定n个整数,使用堆排序将其从小到大排序。
    b.注意事项
        建堆的时间复杂度是O(n),不是O(n log n)。

2.5 二分查找

01.二分查找原理
    a.基本思想
        在有序数组中,每次比较中间元素,缩小一半搜索范围。
    b.时间复杂度
        O(log n),每次减半。
    c.应用场景
        有序数组查找、二分答案、最优化问题。

02.整数二分模板
    a.查找左边界
        ---
        // 查找>=target的最小索引
        public int binarySearchLeft(int[] arr, int target) {
            int l = 0, r = arr.length - 1;
            while (l < r) {
                int mid = l + r >> 1;
                if (arr[mid] >= target) r = mid;
                else l = mid + 1;
            }
            return l;
        }
        ---
    b.查找右边界
        ---
        // 查找<=target的最大索引
        public int binarySearchRight(int[] arr, int target) {
            int l = 0, r = arr.length - 1;
            while (l < r) {
                int mid = l + r + 1 >> 1;
                if (arr[mid] <= target) l = mid;
                else r = mid - 1;
            }
            return l;
        }
        ---

03.浮点数二分
    a.模板
        ---
        public double binarySearchFloat(double l, double r) {
            double eps = 1e-8;
            while (r - l > eps) {
                double mid = (l + r) / 2;
                if (check(mid)) r = mid;
                else l = mid;
            }
            return l;
        }
        ---
    b.AcWing 790数的三次方根
        ---
        public double cubicRoot(double n) {
            double l = -10000, r = 10000;
            while (r - l > 1e-8) {
                double mid = (l + r) / 2;
                if (mid * mid * mid >= n) r = mid;
                else l = mid;
            }
            return l;
        }
        ---

04.二分答案
    a.思想
        将最优化问题转化为判定问题,二分答案范围。
    b.模板
        ---
        public int binaryAnswer() {
            int l = 0, r = Integer.MAX_VALUE;
            while (l < r) {
                int mid = l + r >> 1;
                if (check(mid)) r = mid;
                else l = mid + 1;
            }
            return l;
        }
        ---

2.6 离散化

01.离散化概述
    a.定义
        将无限空间中的有限个体映射到有限空间中,保持元素间的大小关系。
    b.应用场景
        数据范围很大但数据个数较少,如坐标压缩。
    c.核心思想
        用排名代替原始值。

02.离散化步骤
    a.去重排序
        将所有需要离散化的值去重并排序。
    b.二分查找
        对每个原始值,二分查找其在排序后数组中的位置。

03.离散化实现
    a.Java实现
        ---
        public int[] discretize(int[] arr) {
            // 去重排序
            Set<Integer> set = new TreeSet<>();
            for (int num : arr) {
                set.add(num);
            }

            List<Integer> sorted = new ArrayList<>(set);
            Map<Integer, Integer> map = new HashMap<>();
            for (int i = 0; i < sorted.size(); i++) {
                map.put(sorted.get(i), i + 1);
            }

            // 映射
            int[] result = new int[arr.length];
            for (int i = 0; i < arr.length; i++) {
                result[i] = map.get(arr[i]);
            }
            return result;
        }
        ---
    b.AcWing模板
        ---
        List<Integer> alls = new ArrayList<>();  // 存储所有待离散化的值

        // 去重
        Collections.sort(alls);
        int unique = 1;
        for (int i = 1; i < alls.size(); i++) {
            if (!alls.get(i).equals(alls.get(i-1))) {
                alls.set(unique++, alls.get(i));
            }
        }

        // 二分查找离散化后的值
        int find(int x) {
            int l = 0, r = unique - 1;
            while (l < r) {
                int mid = l + r >> 1;
                if (alls.get(mid) >= x) r = mid;
                else l = mid + 1;
            }
            return l + 1;  // 映射到1, 2, 3...
        }
        ---

04.AcWing 802区间和
    a.题目
        在数轴上进行区间加法和区间求和操作,坐标范围很大。
    b.离散化解法
        将所有出现的坐标离散化,然后使用前缀和处理。

2.7 经典题目

01.排序相关
    a.数组中的第K个最大元素
        LeetCode 215 - 使用快速选择算法,时间复杂度O(n)。
        ---
        public int findKthLargest(int[] nums, int k) {
            return quickSelect(nums, 0, nums.length - 1, nums.length - k);
        }

        private int quickSelect(int[] nums, int l, int r, int k) {
            if (l == r) return nums[l];

            int i = l - 1, j = r + 1, x = nums[l + r >> 1];
            while (i < j) {
                do i++; while (nums[i] < x);
                do j--; while (nums[j] > x);
                if (i < j) {
                    int temp = nums[i];
                    nums[i] = nums[j];
                    nums[j] = temp;
                }
            }

            if (k <= j) return quickSelect(nums, l, j, k);
            else return quickSelect(nums, j + 1, r, k);
        }
        ---
    b.合并两个有序数组
        LeetCode 88 - 从后往前合并,避免覆盖。

02.二分查找相关
    a.搜索旋转排序数组
        LeetCode 33 - 先找到旋转点,再二分查找。
    b.寻找峰值
        LeetCode 162 - 二分查找局部最大值。
    c.在排序数组中查找元素的第一个和最后一个位置
        LeetCode 34 - 使用两次二分查找左右边界。

03.逆序对问题
    a.AcWing 788逆序对的数量
        使用归并排序统计逆序对。
    b.剑指Offer 51数组中的逆序对
        同样使用归并排序解决。

3 双指针与区间

3.1 双指针算法

01.双指针概述
    a.基本思想
        使用两个指针遍历数组,通过指针移动优化时间复杂度。
    b.常见类型
        对撞指针、快慢指针、滑动窗口。
    c.时间复杂度
        通常将O(n²)优化为O(n)。

02.对撞指针
    a.两数之和
        ---
        public int[] twoSum(int[] arr, int target) {
            Arrays.sort(arr);
            int l = 0, r = arr.length - 1;
            while (l < r) {
                int sum = arr[l] + arr[r];
                if (sum == target) return new int[]{l, r};
                else if (sum < target) l++;
                else r--;
            }
            return new int[]{-1, -1};
        }
        ---
    b.三数之和
        LeetCode 15 - 排序后固定一个数,双指针找另外两个数。

03.快慢指针
    a.环形链表
        ---
        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;
        }
        ---
    b.链表中点
        快指针走两步,慢指针走一步,快指针到末尾时慢指针在中点。

04.AcWing 799最长连续不重复子序列
    a.题目
        给定整数序列,求最长的连续不重复子序列长度。
    b.双指针解法
        ---
        public int longestSubsequence(int[] arr) {
            int n = arr.length;
            int[] count = new int[100010];
            int maxLen = 0;

            for (int i = 0, j = 0; i < n; i++) {
                count[arr[i]]++;
                while (count[arr[i]] > 1) {
                    count[arr[j]]--;
                    j++;
                }
                maxLen = Math.max(maxLen, i - j + 1);
            }
            return maxLen;
        }
        ---

3.2 滑动窗口

01.滑动窗口概述
    a.基本思想
        维护一个窗口,通过移动窗口的左右边界来解决问题。
    b.适用场景
        连续子数组、子串问题。
    c.时间复杂度
        通常是O(n)。

02.滑动窗口模板
    a.固定窗口大小
        ---
        public void fixedWindow(int[] arr, int k) {
            for (int i = 0; i <= arr.length - k; i++) {
                // 处理窗口[i, i+k-1]
            }
        }
        ---
    b.可变窗口大小
        ---
        public int variableWindow(int[] arr) {
            int left = 0, result = 0;
            for (int right = 0; right < arr.length; right++) {
                // 扩大窗口
                // add arr[right]

                while (/* 窗口不满足条件 */) {
                    // 缩小窗口
                    // remove arr[left]
                    left++;
                }

                // 更新结果
                result = Math.max(result, right - left + 1);
            }
            return result;
        }
        ---

03.经典题目
    a.最小覆盖子串
        ---
        public String minWindow(String s, String t) {
            if (s.length() < t.length()) return "";

            Map<Character, Integer> need = new HashMap<>();
            Map<Character, Integer> window = new HashMap<>();

            for (char c : t.toCharArray()) {
                need.put(c, need.getOrDefault(c, 0) + 1);
            }

            int left = 0, right = 0;
            int valid = 0;
            int start = 0, len = Integer.MAX_VALUE;

            while (right < s.length()) {
                char c = s.charAt(right);
                right++;

                if (need.containsKey(c)) {
                    window.put(c, window.getOrDefault(c, 0) + 1);
                    if (window.get(c).equals(need.get(c))) {
                        valid++;
                    }
                }

                while (valid == need.size()) {
                    if (right - left < len) {
                        start = left;
                        len = right - left;
                    }

                    char d = s.charAt(left);
                    left++;

                    if (need.containsKey(d)) {
                        if (window.get(d).equals(need.get(d))) {
                            valid--;
                        }
                        window.put(d, window.get(d) - 1);
                    }
                }
            }

            return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
        }
        ---
    b.找到字符串中所有字母异位词
        LeetCode 438 - 使用滑动窗口统计字符频率。

3.3 前缀和

01.前缀和概述
    a.定义
        前缀和数组s[i]表示原数组前i个元素的和。
    b.作用
        快速计算区间和,将O(n)优化为O(1)。
    c.公式
        s[i] = s[i-1] + arr[i],区间[l,r]的和为s[r] - s[l-1]。

02.一维前缀和
    a.AcWing 795前缀和
        ---
        public class PrefixSum {
            public static void main(String[] args) {
                Scanner sc = new Scanner(System.in);
                int n = sc.nextInt();
                int m = sc.nextInt();

                int[] arr = new int[n + 1];
                int[] s = new int[n + 1];

                for (int i = 1; i <= n; i++) {
                    arr[i] = sc.nextInt();
                    s[i] = s[i - 1] + arr[i];
                }

                while (m-- > 0) {
                    int l = sc.nextInt();
                    int r = sc.nextInt();
                    System.out.println(s[r] - s[l - 1]);
                }
            }
        }
        ---

03.二维前缀和
    a.定义
        s[i][j]表示从(1,1)到(i,j)的子矩阵元素和。
    b.公式
        s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + arr[i][j]
    c.AcWing 796子矩阵的和
        ---
        public int[][] buildPrefixSum(int[][] matrix) {
            int n = matrix.length, m = matrix[0].length;
            int[][] s = new int[n + 1][m + 1];

            for (int i = 1; i <= n; i++) {
                for (int j = 1; j <= m; j++) {
                    s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + matrix[i-1][j-1];
                }
            }
            return s;
        }

        public int querySum(int[][] s, int x1, int y1, int x2, int y2) {
            return s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1];
        }
        ---

04.应用场景
    a.区间和查询
        多次查询数组区间和。
    b.子数组和问题
        LeetCode 560和为K的子数组 - 使用前缀和+哈希表。
    c.二维矩阵问题
        子矩阵和、最大子矩阵等问题。

3.4 差分数组

01.差分数组概述
    a.定义
        差分数组b[i] = a[i] - a[i-1],是前缀和的逆运算。
    b.作用
        将区间修改操作的时间复杂度从O(n)优化到O(1)。
    c.应用场景
        频繁的区间加减操作。

02.一维差分
    a.构造差分数组
        ---
        public int[] buildDiff(int[] arr) {
            int n = arr.length;
            int[] diff = new int[n + 1];
            for (int i = 0; i < n; i++) {
                diff[i] = arr[i] - (i > 0 ? arr[i-1] : 0);
            }
            return diff;
        }
        ---
    b.区间修改
        ---
        // 给区间[l, r]加上c
        public void rangeAdd(int[] diff, int l, int r, int c) {
            diff[l] += c;
            diff[r + 1] -= c;
        }
        ---
    c.还原数组
        ---
        public int[] restore(int[] diff) {
            int n = diff.length;
            int[] arr = new int[n];
            arr[0] = diff[0];
            for (int i = 1; i < n; i++) {
                arr[i] = arr[i-1] + diff[i];
            }
            return arr;
        }
        ---

03.二维差分
    a.构造差分矩阵
        ---
        public void insert(int[][] diff, int x1, int y1, int x2, int y2, int c) {
            diff[x1][y1] += c;
            diff[x2+1][y1] -= c;
            diff[x1][y2+1] -= c;
            diff[x2+1][y2+1] += c;
        }
        ---
    b.AcWing 798差分矩阵
        给定矩阵,进行多次子矩阵加法操作,最后输出结果矩阵。

04.应用场景
    a.航班预订统计
        LeetCode 1109 - 使用差分数组处理区间加法。
    b.拼车
        LeetCode 1094 - 使用差分数组统计每个位置的乘客数。

3.5 区间合并

01.区间合并概述
    a.问题描述
        给定若干区间,合并所有重叠的区间。
    b.基本思路
        先按左端点排序,然后依次合并。
    c.时间复杂度
        O(n log n),主要是排序的时间。

02.区间合并实现
    a.Java实现
        ---
        public int[][] merge(int[][] intervals) {
            if (intervals.length == 0) return new int[0][0];

            // 按左端点排序
            Arrays.sort(intervals, (a, b) -> a[0] - b[0]);

            List<int[]> result = new ArrayList<>();
            int[] current = intervals[0];

            for (int i = 1; i < intervals.length; i++) {
                if (intervals[i][0] <= current[1]) {
                    // 有重叠,合并
                    current[1] = Math.max(current[1], intervals[i][1]);
                } else {
                    // 无重叠,加入结果
                    result.add(current);
                    current = intervals[i];
                }
            }
            result.add(current);

            return result.toArray(new int[result.size()][]);
        }
        ---
    b.Go实现
        ---
        func merge(intervals [][]int) [][]int {
            if len(intervals) == 0 {
                return [][]int{}
            }

            sort.Slice(intervals, func(i, j int) bool {
                return intervals[i][0] < intervals[j][0]
            })

            result := [][]int{intervals[0]}

            for i := 1; i < len(intervals); i++ {
                last := result[len(result)-1]
                if intervals[i][0] <= last[1] {
                    last[1] = max(last[1], intervals[i][1])
                } else {
                    result = append(result, intervals[i])
                }
            }

            return result
        }
        ---

03.AcWing 803区间合并
    a.题目
        给定n个区间,合并所有有交集的区间。
    b.输入格式
        第一行包含整数n,接下来n行每行包含两个整数l和r。
    c.输出格式
        输出合并后的区间数量。

04.相关题目
    a.插入区间
        LeetCode 57 - 在已排序的区间列表中插入新区间。
    b.无重叠区间
        LeetCode 435 - 计算需要移除多少个区间使得剩余区间不重叠。

3.6 位运算技巧

01.基本位运算
    a.常用操作
        a.与运算
            x & 1判断奇偶,x & (x-1)去掉最低位的1。
        b.或运算
            x | (1 << k)将第k位设为1。
        c.异或运算
            x ^ x = 0,x ^ 0 = x,可用于交换变量。
        d.左移右移
            x << k相当于乘以2的k次方,x >> k相当于除以2的k次方。

02.常用技巧
    a.判断第k位是否为1
        ---
        boolean isBitSet(int x, int k) {
            return (x & (1 << k)) != 0;
        }
        ---
    b.统计1的个数
        ---
        int countOnes(int x) {
            int count = 0;
            while (x != 0) {
                x &= (x - 1);
                count++;
            }
            return count;
        }
        ---
    c.lowbit操作
        ---
        int lowbit(int x) {
            return x & -x;  // 返回最低位的1
        }
        ---

03.位运算应用
    a.只出现一次的数字
        ---
        // 其他数字都出现两次
        public int singleNumber(int[] nums) {
            int result = 0;
            for (int num : nums) {
                result ^= num;
            }
            return result;
        }
        ---
    b.2的幂
        ---
        public boolean isPowerOfTwo(int n) {
            return n > 0 && (n & (n - 1)) == 0;
        }
        ---
    c.颠倒二进制位
        ---
        public int reverseBits(int n) {
            int result = 0;
            for (int i = 0; i < 32; i++) {
                result = (result << 1) | (n & 1);
                n >>= 1;
            }
            return result;
        }
        ---

04.AcWing 801二进制中1的个数
    a.题目
        给定n个非负整数,统计每个数的二进制表示中1的个数。
    b.lowbit解法
        ---
        public int countOnes(int x) {
            int count = 0;
            while (x != 0) {
                x -= x & -x;
                count++;
            }
            return count;
        }
        ---

3.7 经典题目

01.双指针题目
    a.盛最多水的容器
        LeetCode 11 - 双指针从两端向中间移动。
        ---
        public int maxArea(int[] height) {
            int left = 0, right = height.length - 1;
            int maxWater = 0;

            while (left < right) {
                int h = Math.min(height[left], height[right]);
                maxWater = Math.max(maxWater, h * (right - left));

                if (height[left] < height[right]) {
                    left++;
                } else {
                    right--;
                }
            }
            return maxWater;
        }
        ---
    b.删除有序数组中的重复项
        LeetCode 26 - 快慢指针原地去重。

02.前缀和题目
    a.和为K的子数组
        LeetCode 560 - 前缀和+哈希表。
        ---
        public int subarraySum(int[] nums, int k) {
            Map<Integer, Integer> map = new HashMap<>();
            map.put(0, 1);
            int sum = 0, count = 0;

            for (int num : nums) {
                sum += num;
                count += map.getOrDefault(sum - k, 0);
                map.put(sum, map.getOrDefault(sum, 0) + 1);
            }
            return count;
        }
        ---
    b.连续的子数组和
        LeetCode 523 - 前缀和取模+哈希表。

03.区间问题
    a.会议室II
        LeetCode 253 - 计算最少需要多少个会议室。
    b.用最少数量的箭引爆气球
        LeetCode 452 - 区间贪心问题。

4 基础数据结构

4.1 链表与邻接表

01.单链表应用
    a.AcWing 826单链表
        使用数组模拟单链表,支持头插、删除、插入操作。
        ---
        int N = 100010;
        int[] e = new int[N];  // 节点值
        int[] ne = new int[N]; // next指针
        int head = -1, idx = 0;

        // 头插
        void addToHead(int x) {
            e[idx] = x;
            ne[idx] = head;
            head = idx++;
        }

        // 在第k个插入的数后面插入x
        void add(int k, int x) {
            e[idx] = x;
            ne[idx] = ne[k];
            ne[k] = idx++;
        }

        // 删除第k个插入的数后面的数
        void remove(int k) {
            ne[k] = ne[ne[k]];
        }
        ---

02.双链表应用
    a.AcWing 827双链表
        ---
        int N = 100010;
        int[] e = new int[N];
        int[] l = new int[N];  // 左指针
        int[] r = new int[N];  // 右指针
        int idx = 2;

        void init() {
            r[0] = 1;
            l[1] = 0;
        }

        // 在第k个数右边插入x
        void add(int k, int x) {
            e[idx] = x;
            r[idx] = r[k];
            l[idx] = k;
            l[r[k]] = idx;
            r[k] = idx++;
        }

        // 删除第k个数
        void remove(int k) {
            r[l[k]] = r[k];
            l[r[k]] = l[k];
        }
        ---

03.邻接表存储图
    a.定义
        邻接表是一种常用的图存储方式,每个节点维护一个链表存储所有邻接边。
    b.实现
        使用数组模拟链表,h[i]存储节点i的第一条边的索引。

4.2 栈与队列应用

01.单调栈应用
    a.AcWing 830单调栈
        给定序列,求每个数左边第一个比它小的数。
        ---
        public int[] findLeftSmaller(int[] arr) {
            int n = arr.length;
            int[] result = new int[n];
            Stack<Integer> stack = new Stack<>();

            for (int i = 0; i < n; i++) {
                while (!stack.isEmpty() && stack.peek() >= arr[i]) {
                    stack.pop();
                }
                result[i] = stack.isEmpty() ? -1 : stack.peek();
                stack.push(arr[i]);
            }
            return result;
        }
        ---

02.单调队列应用
    a.AcWing 154滑动窗口
        给定数组和窗口大小k,求每个窗口的最小值和最大值。
        ---
        public int[] maxSlidingWindow(int[] nums, int k) {
            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.peekFirst() < i - k + 1) {
                    deque.pollFirst();
                }

                // 维护单调递减队列
                while (!deque.isEmpty() && nums[i] > nums[deque.peekLast()]) {
                    deque.pollLast();
                }

                deque.offerLast(i);

                if (i >= k - 1) {
                    result[i - k + 1] = nums[deque.peekFirst()];
                }
            }
            return result;
        }
        ---

03.表达式求值
    a.AcWing 3302表达式求值
        使用两个栈,一个存数字,一个存运算符。

4.3 并查集

01.并查集概述
    a.定义
        并查集是一种树形数据结构,用于处理不相交集合的合并和查询问题。
    b.核心操作
        find查找根节点、union合并两个集合。
    c.时间复杂度
        路径压缩和按秩合并后,单次操作接近O(1)。

02.AcWing并查集模板
    a.Java实现
        ---
        class UnionFind {
            int[] parent;

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

            // 查找根节点(路径压缩)
            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 px = find(x);
                int py = find(y);
                if (px != py) {
                    parent[px] = py;
                }
            }

            // 判断是否在同一集合
            public boolean isConnected(int x, int y) {
                return find(x) == find(y);
            }
        }
        ---
    b.Go实现
        ---
        type UnionFind struct {
            parent []int
        }

        func NewUnionFind(n int) *UnionFind {
            parent := make([]int, n)
            for i := range parent {
                parent[i] = i
            }
            return &UnionFind{parent}
        }

        func (uf *UnionFind) Find(x int) int {
            if uf.parent[x] != x {
                uf.parent[x] = uf.Find(uf.parent[x])
            }
            return uf.parent[x]
        }

        func (uf *UnionFind) Union(x, y int) {
            px, py := uf.Find(x), uf.Find(y)
            if px != py {
                uf.parent[px] = py
            }
        }
        ---

03.AcWing 836合并集合
    a.题目
        维护n个集合,支持合并和查询操作。
    b.输入格式
        第一行包含n和m,接下来m行每行包含一个操作。
    c.操作类型
        M a b表示合并a和b所在集合,Q a b查询a和b是否在同一集合。

04.应用场景
    a.连通性问题
        判断图中两点是否连通。
    b.最小生成树
        Kruskal算法使用并查集判断是否成环。
    c.朋友圈问题
        LeetCode 547朋友圈 - 统计连通分量个数。

4.4 堆的应用

01.Top K问题
    a.第K大元素
        使用最小堆维护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();
        }
        ---
    b.前K个高频元素
        LeetCode 347 - 使用哈希表统计频率,堆找Top K。
        ---
        public int[] topKFrequent(int[] nums, int k) {
            Map<Integer, Integer> count = new HashMap<>();
            for (int num : nums) {
                count.put(num, count.getOrDefault(num, 0) + 1);
            }

            PriorityQueue<Integer> heap = new PriorityQueue<>(
                (a, b) -> count.get(a) - count.get(b)
            );

            for (int num : count.keySet()) {
                heap.offer(num);
                if (heap.size() > k) {
                    heap.poll();
                }
            }

            int[] result = new int[k];
            for (int i = 0; i < k; i++) {
                result[i] = heap.poll();
            }
            return result;
        }
        ---

02.合并K个有序链表
    a.问题描述
        LeetCode 23 - 合并k个升序链表。
    b.堆解法
        ---
        public ListNode mergeKLists(ListNode[] lists) {
            PriorityQueue<ListNode> heap = new PriorityQueue<>(
                (a, b) -> a.val - b.val
            );

            for (ListNode list : lists) {
                if (list != null) {
                    heap.offer(list);
                }
            }

            ListNode dummy = new ListNode(0);
            ListNode curr = dummy;

            while (!heap.isEmpty()) {
                ListNode node = heap.poll();
                curr.next = node;
                curr = curr.next;
                if (node.next != null) {
                    heap.offer(node.next);
                }
            }

            return dummy.next;
        }
        ---

03.数据流中的中位数
    a.问题描述
        LeetCode 295 - 动态维护数据流的中位数。
    b.双堆解法
        ---
        class MedianFinder {
            PriorityQueue<Integer> maxHeap;  // 存储较小的一半
            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() + minHeap.peek()) / 2.0;
                } else {
                    return maxHeap.peek();
                }
            }
        }
        ---

04.AcWing 838堆排序
    a.题目
        维护一个小根堆,支持插入、输出最小值、删除最小值操作。
    b.手写堆实现
        使用数组模拟堆,实现上浮下沉操作。

4.5 哈希表应用

01.两数之和变形
    a.两数之和
        LeetCode 1 - 使用哈希表存储已遍历的数字。
    b.三数之和
        LeetCode 15 - 排序后固定一个数,双指针找另外两个数。
    c.四数之和
        LeetCode 18 - 固定两个数,双指针找另外两个数。

02.字母异位词
    a.有效的字母异位词
        LeetCode 242 - 使用哈希表统计字符频率。
        ---
        public boolean isAnagram(String s, String t) {
            if (s.length() != t.length()) return false;
            int[] count = new int[26];
            for (int i = 0; i < s.length(); i++) {
                count[s.charAt(i) - 'a']++;
                count[t.charAt(i) - 'a']--;
            }
            for (int c : count) {
                if (c != 0) return false;
            }
            return true;
        }
        ---
    b.字母异位词分组
        LeetCode 49 - 使用排序后的字符串作为key。

03.LRU缓存
    a.问题描述
        LeetCode 146 - 实现LRU缓存机制。
    b.HashMap + 双向链表
        ---
        class LRUCache {
            class Node {
                int key, value;
                Node prev, next;
            }

            private Map<Integer, Node> map;
            private Node head, tail;
            private int capacity;

            public LRUCache(int capacity) {
                this.capacity = capacity;
                map = new HashMap<>();
                head = new Node();
                tail = new Node();
                head.next = tail;
                tail.prev = head;
            }

            public int get(int key) {
                if (!map.containsKey(key)) return -1;
                Node node = map.get(key);
                moveToHead(node);
                return node.value;
            }

            public void put(int key, int value) {
                if (map.containsKey(key)) {
                    Node node = map.get(key);
                    node.value = value;
                    moveToHead(node);
                } else {
                    Node node = new Node();
                    node.key = key;
                    node.value = value;
                    map.put(key, node);
                    addToHead(node);
                    if (map.size() > capacity) {
                        Node removed = removeTail();
                        map.remove(removed.key);
                    }
                }
            }

            private void moveToHead(Node node) {
                removeNode(node);
                addToHead(node);
            }

            private void removeNode(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;
            }

            private Node removeTail() {
                Node node = tail.prev;
                removeNode(node);
                return node;
            }
        }
        ---

04.AcWing 840模拟散列表
    a.拉链法
        使用数组加链表处理冲突。
    b.开放寻址法
        使用线性探测处理冲突。

4.6 字符串算法

01.KMP算法
    a.next数组
        ---
        private int[] getNext(String pattern) {
            int m = pattern.length();
            int[] next = new int[m];
            int j = 0;

            for (int i = 1; i < m; i++) {
                while (j > 0 && pattern.charAt(i) != pattern.charAt(j)) {
                    j = next[j - 1];
                }
                if (pattern.charAt(i) == pattern.charAt(j)) {
                    j++;
                }
                next[i] = j;
            }
            return next;
        }
        ---
    b.字符串匹配
        ---
        public int strStr(String haystack, String needle) {
            if (needle.isEmpty()) return 0;
            int[] next = getNext(needle);
            int j = 0;

            for (int i = 0; i < haystack.length(); i++) {
                while (j > 0 && haystack.charAt(i) != needle.charAt(j)) {
                    j = next[j - 1];
                }
                if (haystack.charAt(i) == needle.charAt(j)) {
                    j++;
                }
                if (j == needle.length()) {
                    return i - j + 1;
                }
            }
            return -1;
        }
        ---

02.Trie树
    a.实现
        ---
        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;
            }
        }
        ---

03.字符串哈希
    a.AcWing 841字符串哈希
        使用多项式哈希快速比较子串。
        ---
        long P = 131, MOD = (long)1e9 + 7;
        long[] hash, pow;

        public void buildHash(String s) {
            int n = s.length();
            hash = new long[n + 1];
            pow = new long[n + 1];
            pow[0] = 1;

            for (int i = 1; i <= n; i++) {
                hash[i] = (hash[i-1] * P + s.charAt(i-1)) % MOD;
                pow[i] = (pow[i-1] * P) % MOD;
            }
        }

        public long getHash(int l, int r) {
            return (hash[r] - hash[l-1] * pow[r-l+1] % MOD + MOD) % MOD;
        }
        ---

04.Manacher算法
    a.最长回文子串
        O(n)时间复杂度求最长回文子串。

4.7 经典题目

01.数据结构设计题
    a.最小栈
        LeetCode 155 - 使用辅助栈存储最小值。
    b.用栈实现队列
        LeetCode 232 - 使用两个栈实现队列。
    c.用队列实现栈
        LeetCode 225 - 使用两个队列实现栈。

02.并查集题目
    a.朋友圈
        LeetCode 547 - 统计连通分量个数。
    b.冗余连接
        LeetCode 684 - 找出使树变成图的边。
    c.账户合并
        LeetCode 721 - 合并相同邮箱的账户。

03.字符串题目
    a.最长回文子串
        LeetCode 5 - 中心扩展法或动态规划。
    b.字符串相加
        LeetCode 415 - 模拟大数加法。
    c.反转字符串中的单词
        LeetCode 151 - 双指针或split方法。

04.综合题目
    a.LFU缓存
        LeetCode 460 - 实现LFU缓存机制。
    b.全O(1)的数据结构
        LeetCode 432 - 支持O(1)时间的inc、dec、getMaxKey、getMinKey。

5 搜索基础

5.1 DFS深度优先搜索

01.DFS概述
    a.基本思想
        沿着一条路径深入搜索,直到无法继续,然后回溯到上一个节点继续搜索。
    b.实现方式
        递归或栈实现。
    c.时间复杂度
        O(n!)或O(2ⁿ),取决于问题规模。

02.DFS模板
    a.递归模板
        ---
        boolean[] visited;

        public void dfs(int u) {
            visited[u] = true;

            // 处理当前节点

            // 遍历所有可能的下一步
            for (int next : getNext(u)) {
                if (!visited[next]) {
                    dfs(next);
                }
            }

            // 回溯(如果需要)
            visited[u] = false;
        }
        ---

03.AcWing 842排列数字
    a.题目
        给定整数n,输出1到n的全排列。
    b.DFS解法
        ---
        int n;
        int[] path;
        boolean[] used;

        public void dfs(int u) {
            if (u == n) {
                // 输出当前排列
                for (int i = 0; i < n; i++) {
                    System.out.print(path[i] + " ");
                }
                System.out.println();
                return;
            }

            for (int i = 1; i <= n; i++) {
                if (!used[i]) {
                    path[u] = i;
                    used[i] = true;
                    dfs(u + 1);
                    used[i] = false;  // 回溯
                }
            }
        }
        ---

04.N皇后问题
    a.问题描述
        在n×n的棋盘上放置n个皇后,使得任意两个皇后不在同一行、列、对角线。
    b.DFS解法
        ---
        int n;
        boolean[] col, dg, udg;
        char[][] board;

        public void dfs(int u) {
            if (u == n) {
                // 输出方案
                printBoard();
                return;
            }

            for (int i = 0; i < n; i++) {
                if (!col[i] && !dg[u + i] && !udg[n - u + i]) {
                    board[u][i] = 'Q';
                    col[i] = dg[u + i] = udg[n - u + i] = true;
                    dfs(u + 1);
                    col[i] = dg[u + i] = udg[n - u + i] = false;
                    board[u][i] = '.';
                }
            }
        }
        ---

5.2 BFS广度优先搜索

01.BFS概述
    a.基本思想
        按层次遍历,先访问距离起点近的节点,再访问距离远的节点。
    b.实现方式
        使用队列实现。
    c.应用场景
        最短路径、层序遍历、连通性问题。

02.BFS模板
    a.Java实现
        ---
        public void bfs(int start) {
            Queue<Integer> queue = new LinkedList<>();
            boolean[] visited = new boolean[n];

            queue.offer(start);
            visited[start] = true;

            while (!queue.isEmpty()) {
                int u = queue.poll();

                // 处理当前节点

                // 遍历所有邻接节点
                for (int v : getNeighbors(u)) {
                    if (!visited[v]) {
                        visited[v] = true;
                        queue.offer(v);
                    }
                }
            }
        }
        ---
    b.Go实现
        ---
        func bfs(start int, n int) {
            queue := []int{start}
            visited := make([]bool, n)
            visited[start] = true

            for len(queue) > 0 {
                u := queue[0]
                queue = queue[1:]

                // 处理当前节点

                for _, v := range getNeighbors(u) {
                    if !visited[v] {
                        visited[v] = true
                        queue = append(queue, v)
                    }
                }
            }
        }
        ---

03.AcWing 844走迷宫
    a.题目
        给定n×m的迷宫,求从(1,1)到(n,m)的最短路径长度。
    b.BFS解法
        ---
        int[][] dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};

        public int bfs(int[][] maze) {
            int n = maze.length, m = maze[0].length;
            Queue<int[]> queue = new LinkedList<>();
            int[][] dist = new int[n][m];

            for (int[] row : dist) Arrays.fill(row, -1);

            queue.offer(new int[]{0, 0});
            dist[0][0] = 0;

            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 < n && ny >= 0 && ny < m &&
                        maze[nx][ny] == 0 && dist[nx][ny] == -1) {
                        dist[nx][ny] = dist[x][y] + 1;
                        queue.offer(new int[]{nx, ny});
                    }
                }
            }
            return dist[n-1][m-1];
        }
        ---

04.DFS与BFS对比
    a.DFS
        空间复杂度O(h),适合求所有方案、判断连通性。
    b.BFS
        空间复杂度O(n),适合求最短路径、层序遍历。

5.3 树的遍历

01.前序遍历
    a.递归实现
        ---
        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);
        }
        ---
    b.迭代实现
        ---
        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;
        }
        ---

02.中序遍历
    a.递归实现
        ---
        public List<Integer> inorderTraversal(TreeNode root) {
            List<Integer> result = new ArrayList<>();
            inorder(root, result);
            return result;
        }

        private void inorder(TreeNode node, List<Integer> result) {
            if (node == null) return;
            inorder(node.left, result);
            result.add(node.val);
            inorder(node.right, result);
        }
        ---
    b.迭代实现
        ---
        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;
        }
        ---

03.后序遍历
    a.递归实现
        ---
        public List<Integer> postorderTraversal(TreeNode root) {
            List<Integer> result = new ArrayList<>();
            postorder(root, result);
            return result;
        }

        private void postorder(TreeNode node, List<Integer> result) {
            if (node == null) return;
            postorder(node.left, result);
            postorder(node.right, result);
            result.add(node.val);
        }
        ---

04.层序遍历
    a.BFS实现
        ---
        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;
        }
        ---

5.4 图的遍历

01.DFS遍历图
    a.邻接表实现
        ---
        List<Integer>[] graph;
        boolean[] visited;

        public void dfs(int u) {
            visited[u] = true;
            System.out.print(u + " ");

            for (int v : graph[u]) {
                if (!visited[v]) {
                    dfs(v);
                }
            }
        }
        ---
    b.邻接矩阵实现
        ---
        int[][] graph;
        boolean[] visited;

        public void dfs(int u, int n) {
            visited[u] = true;
            System.out.print(u + " ");

            for (int v = 0; v < n; v++) {
                if (graph[u][v] == 1 && !visited[v]) {
                    dfs(v, n);
                }
            }
        }
        ---

02.BFS遍历图
    a.实现
        ---
        public void bfs(int start) {
            Queue<Integer> queue = new LinkedList<>();
            boolean[] visited = new boolean[n];

            queue.offer(start);
            visited[start] = true;

            while (!queue.isEmpty()) {
                int u = queue.poll();
                System.out.print(u + " ");

                for (int v : graph[u]) {
                    if (!visited[v]) {
                        visited[v] = true;
                        queue.offer(v);
                    }
                }
            }
        }
        ---

03.判断图的连通性
    a.无向图
        从任意节点开始DFS,检查是否访问所有节点。
    b.有向图
        需要检查强连通性,使用Tarjan或Kosaraju算法。

04.AcWing 846树的重心
    a.题目
        找到树的重心,使得删除后各连通块的最大值最小。
    b.DFS解法
        ---
        int n, ans = Integer.MAX_VALUE;
        List<Integer>[] graph;
        boolean[] visited;

        public int dfs(int u) {
            visited[u] = true;
            int size = 1, maxPart = 0;

            for (int v : graph[u]) {
                if (!visited[v]) {
                    int subSize = dfs(v);
                    size += subSize;
                    maxPart = Math.max(maxPart, subSize);
                }
            }

            maxPart = Math.max(maxPart, n - size);
            ans = Math.min(ans, maxPart);
            return size;
        }
        ---

5.5 拓扑排序

01.拓扑排序概述
    a.定义
        对有向无环图DAG的所有节点进行线性排序,使得对于任意边(u,v),u在v之前。
    b.应用场景
        任务调度、课程安排、依赖关系。
    c.前提条件
        图必须是有向无环图。

02.Kahn算法
    a.基本思想
        不断选择入度为0的节点加入结果,并删除该节点及其出边。
    b.Java实现
        ---
        public int[] findOrder(int numCourses, int[][] prerequisites) {
            int[] inDegree = new int[numCourses];
            List<Integer>[] graph = new ArrayList[numCourses];

            for (int i = 0; i < numCourses; i++) {
                graph[i] = new ArrayList<>();
            }

            for (int[] pre : prerequisites) {
                graph[pre[1]].add(pre[0]);
                inDegree[pre[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 u = queue.poll();
                result[index++] = u;

                for (int v : graph[u]) {
                    inDegree[v]--;
                    if (inDegree[v] == 0) {
                        queue.offer(v);
                    }
                }
            }

            return index == numCourses ? result : new int[0];
        }
        ---

03.DFS拓扑排序
    a.基本思想
        使用DFS遍历,将节点按后序遍历的逆序排列。
    b.实现
        ---
        List<Integer>[] graph;
        boolean[] visited;
        Stack<Integer> stack;

        public int[] topologicalSort(int n) {
            visited = new boolean[n];
            stack = new Stack<>();

            for (int i = 0; i < n; i++) {
                if (!visited[i]) {
                    dfs(i);
                }
            }

            int[] result = new int[n];
            for (int i = 0; i < n; i++) {
                result[i] = stack.pop();
            }
            return result;
        }

        private void dfs(int u) {
            visited[u] = true;
            for (int v : graph[u]) {
                if (!visited[v]) {
                    dfs(v);
                }
            }
            stack.push(u);
        }
        ---

04.课程表系列
    a.课程表I
        LeetCode 207 - 判断是否可以完成所有课程。
    b.课程表II
        LeetCode 210 - 返回课程的学习顺序。
    c.课程表III
        LeetCode 630 - 最多可以上多少门课。

5.6 搜索优化技巧

01.剪枝优化
    a.可行性剪枝
        判断当前状态是否可行,不可行则提前返回。
    b.最优性剪枝
        判断当前状态是否可能得到更优解,不可能则提前返回。
    c.记忆化剪枝
        存储已计算的状态,避免重复计算。

02.搜索顺序优化
    a.优先搜索约束多的分支
        先处理限制条件多的情况,减少搜索空间。
    b.启发式搜索
        使用估价函数指导搜索方向。

03.双向搜索
    a.基本思想
        从起点和终点同时搜索,在中间相遇。
    b.适用场景
        搜索空间很大,但搜索深度较小。

04.迭代加深
    a.基本思想
        限制搜索深度,逐步增加深度限制。
    b.优点
        结合了DFS的空间优势和BFS的最优性。
    c.实现
        ---
        int maxDepth;

        public boolean dfs(int depth, int limit) {
            if (depth > limit) return false;
            if (isGoal()) return true;

            for (int next : getNextStates()) {
                if (dfs(depth + 1, limit)) {
                    return true;
                }
            }
            return false;
        }

        public void iterativeDeepening() {
            for (maxDepth = 1; maxDepth <= MAX; maxDepth++) {
                if (dfs(0, maxDepth)) {
                    break;
                }
            }
        }
        ---

5.7 经典题目

01.N皇后问题
    a.问题描述
        在n×n的棋盘上放置n个皇后,使得任意两个皇后不在同一行、列、对角线。
    b.回溯解法
        LeetCode 51 - 使用DFS枚举所有可能的放置方案。

02.全排列
    a.问题描述
        LeetCode 46 - 给定数组,返回所有可能的全排列。
    b.回溯解法
        使用visited数组标记已使用的元素。

03.组合总和
    a.问题描述
        LeetCode 39 - 找出所有和为target的组合。
    b.回溯解法
        使用DFS枚举所有可能的组合。

04.单词搜索
    a.问题描述
        LeetCode 79 - 在二维网格中搜索单词。
    b.DFS解法
        使用回溯在网格中搜索路径。

6 图论基础

6.1 图的存储

01.邻接矩阵
    a.定义
        使用二维数组存储图,g[i][j]表示i到j的边权。
    b.优点
        查询边是否存在O(1),适合稠密图。
    c.缺点
        空间复杂度O(n²),不适合稀疏图。
    d.Java实现
        ---
        int[][] g = new int[n][n];

        // 添加边
        g[u][v] = w;

        // 无向图
        g[u][v] = g[v][u] = w;
        ---

02.邻接表
    a.定义
        使用链表数组存储图,每个节点维护一个链表存储所有邻接边。
    b.优点
        空间复杂度O(n+m),适合稀疏图。
    c.缺点
        查询边是否存在O(度数)。
    d.Java实现
        ---
        List<Integer>[] g = new ArrayList[n];
        for (int i = 0; i < n; i++) {
            g[i] = new ArrayList<>();
        }

        // 添加边
        g[u].add(v);

        // 带权图
        class Edge {
            int to, weight;
            Edge(int to, int weight) {
                this.to = to;
                this.weight = weight;
            }
        }
        List<Edge>[] g = new ArrayList[n];
        ---

03.AcWing链式前向星
    a.定义
        使用数组模拟链表,适合竞赛中快速建图。
    b.Java实现
        ---
        int N = 100010, M = 200010;
        int[] h = new int[N];  // 头节点
        int[] e = new int[M];  // 边的终点
        int[] ne = new int[M]; // 下一条边
        int[] w = new int[M];  // 边权
        int idx = 0;

        // 初始化
        Arrays.fill(h, -1);

        // 添加边
        void add(int a, int b, int c) {
            e[idx] = b;
            w[idx] = c;
            ne[idx] = h[a];
            h[a] = idx++;
        }

        // 遍历节点u的所有邻接边
        for (int i = h[u]; i != -1; i = ne[i]) {
            int v = e[i];
            int weight = w[i];
            // 处理边(u, v)
        }
        ---

6.2 最短路算法

01.Dijkstra算法
    a.适用场景
        单源最短路,边权非负。
    b.时间复杂度
        朴素版O(n²),堆优化版O(m log n)。
    c.AcWing模板
        ---
        int N = 510;
        int[][] g = new int[N][N];
        int[] dist = new int[N];
        boolean[] visited = new boolean[N];

        public int dijkstra(int n, int start) {
            Arrays.fill(dist, Integer.MAX_VALUE);
            dist[start] = 0;

            for (int i = 0; i < n; i++) {
                int u = -1;
                for (int j = 1; j <= n; j++) {
                    if (!visited[j] && (u == -1 || dist[j] < dist[u])) {
                        u = j;
                    }
                }

                visited[u] = true;

                for (int v = 1; v <= n; v++) {
                    if (g[u][v] != Integer.MAX_VALUE) {
                        dist[v] = Math.min(dist[v], dist[u] + g[u][v]);
                    }
                }
            }

            return dist[n] == Integer.MAX_VALUE ? -1 : dist[n];
        }
        ---

02.Bellman-Ford算法
    a.适用场景
        单源最短路,可以处理负权边,可以检测负环。
    b.时间复杂度
        O(nm)。
    c.模板
        ---
        class Edge {
            int from, to, weight;
        }

        public int bellmanFord(int n, Edge[] edges, int start) {
            int[] dist = new int[n + 1];
            Arrays.fill(dist, Integer.MAX_VALUE);
            dist[start] = 0;

            // 迭代n-1次
            for (int i = 0; i < n - 1; i++) {
                for (Edge e : edges) {
                    if (dist[e.from] != Integer.MAX_VALUE) {
                        dist[e.to] = Math.min(dist[e.to], dist[e.from] + e.weight);
                    }
                }
            }

            return dist[n];
        }
        ---

03.Floyd算法
    a.适用场景
        多源最短路,求任意两点间最短路。
    b.时间复杂度
        O(n³)。
    c.模板
        ---
        public void floyd(int[][] g, int n) {
            for (int k = 1; k <= n; k++) {
                for (int i = 1; i <= n; i++) {
                    for (int j = 1; j <= n; j++) {
                        g[i][j] = Math.min(g[i][j], g[i][k] + g[k][j]);
                    }
                }
            }
        }
        ---

04.SPFA算法
    a.定义
        Bellman-Ford的队列优化版本。
    b.时间复杂度
        平均O(m),最坏O(nm)。
    c.模板
        ---
        public int spfa(int n, int start) {
            int[] dist = new int[n + 1];
            boolean[] inQueue = new boolean[n + 1];
            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];
        }
        ---

6.3 最小生成树

01.最小生成树概述
    a.定义
        连接图中所有节点的边的集合,使得总权值最小。
    b.性质
        有n个节点的最小生成树有n-1条边。
    c.算法
        Prim算法、Kruskal算法。

02.Prim算法
    a.基本思想
        从一个节点开始,每次选择权值最小的边加入生成树。
    b.Java实现
        ---
        public int prim(int[][] graph, int n) {
            boolean[] inMST = new boolean[n];
            int[] dist = new int[n];
            Arrays.fill(dist, Integer.MAX_VALUE);
            dist[0] = 0;
            int totalCost = 0;

            for (int i = 0; i < n; i++) {
                int u = -1;
                for (int j = 0; j < n; j++) {
                    if (!inMST[j] && (u == -1 || dist[j] < dist[u])) {
                        u = j;
                    }
                }

                inMST[u] = true;
                totalCost += dist[u];

                for (int v = 0; v < n; v++) {
                    if (!inMST[v] && graph[u][v] < dist[v]) {
                        dist[v] = graph[u][v];
                    }
                }
            }
            return totalCost;
        }
        ---

03.Kruskal算法
    a.基本思想
        将所有边按权值排序,依次加入边,使用并查集判断是否成环。
    b.Java实现
        ---
        class Edge {
            int u, v, weight;
            Edge(int u, int v, int weight) {
                this.u = u;
                this.v = v;
                this.weight = weight;
            }
        }

        public int kruskal(int n, Edge[] edges) {
            Arrays.sort(edges, (a, b) -> a.weight - b.weight);
            UnionFind uf = new UnionFind(n);
            int totalCost = 0;
            int edgeCount = 0;

            for (Edge edge : edges) {
                if (uf.find(edge.u) != uf.find(edge.v)) {
                    uf.union(edge.u, edge.v);
                    totalCost += edge.weight;
                    edgeCount++;
                    if (edgeCount == n - 1) break;
                }
            }
            return totalCost;
        }
        ---

6.4 二分图

01.二分图定义
    a.基本概念
        图的节点可以分为两个集合,所有边的两个端点分别属于不同集合。
    b.判定方法
        染色法、BFS染色。
    c.性质
        二分图不存在奇数环。

02.染色法判定二分图
    a.基本思想
        使用DFS或BFS给节点染色,相邻节点染不同颜色。
    b.Java实现
        ---
        int[] color;
        List<Integer>[] graph;

        public boolean isBipartite(int[][] graph) {
            int n = graph.length;
            color = new int[n];
            Arrays.fill(color, -1);

            for (int i = 0; i < n; i++) {
                if (color[i] == -1) {
                    if (!dfs(i, 0, graph)) {
                        return false;
                    }
                }
            }
            return true;
        }

        private boolean dfs(int u, int c, int[][] graph) {
            color[u] = c;
            for (int v : graph[u]) {
                if (color[v] == -1) {
                    if (!dfs(v, 1 - c, graph)) {
                        return false;
                    }
                } else if (color[v] == c) {
                    return false;
                }
            }
            return true;
        }
        ---

03.BFS染色
    a.实现
        ---
        public boolean isBipartite(int[][] graph) {
            int n = graph.length;
            int[] color = new int[n];
            Arrays.fill(color, -1);

            for (int i = 0; i < n; i++) {
                if (color[i] == -1) {
                    Queue<Integer> queue = new LinkedList<>();
                    queue.offer(i);
                    color[i] = 0;

                    while (!queue.isEmpty()) {
                        int u = queue.poll();
                        for (int v : graph[u]) {
                            if (color[v] == -1) {
                                color[v] = 1 - color[u];
                                queue.offer(v);
                            } else if (color[v] == color[u]) {
                                return false;
                            }
                        }
                    }
                }
            }
            return true;
        }
        ---

6.5 染色法与匈牙利算法

01.二分图最大匹配
    a.定义
        在二分图中找到最多的边,使得没有两条边共享端点。
    b.应用场景
        任务分配、婚配问题。

02.匈牙利算法
    a.基本思想
        使用DFS为每个左侧节点寻找匹配,如果匹配失败则尝试为已匹配节点重新匹配。
    b.Java实现
        ---
        int[] match;
        boolean[] visited;
        List<Integer>[] graph;

        public int maxMatching(int n, int m) {
            match = new int[m];
            Arrays.fill(match, -1);
            int count = 0;

            for (int i = 0; i < n; i++) {
                visited = new boolean[m];
                if (dfs(i)) {
                    count++;
                }
            }
            return count;
        }

        private boolean dfs(int u) {
            for (int v : graph[u]) {
                if (!visited[v]) {
                    visited[v] = true;
                    if (match[v] == -1 || dfs(match[v])) {
                        match[v] = u;
                        return true;
                    }
                }
            }
            return false;
        }
        ---

03.AcWing 861二分图的最大匹配
    a.题目
        给定二分图,求最大匹配数。
    b.匈牙利算法解决
        时间复杂度O(nm)。

6.6 图论综合应用

01.网络延迟时间
    a.问题描述
        LeetCode 743 - 计算信号传播到所有节点的最短时间。
    b.Dijkstra解法
        单源最短路问题。

02.最便宜的航班
    a.问题描述
        LeetCode 787 - 最多k站中转的最便宜价格。
    b.Bellman-Ford解法
        限制边数的最短路问题。

03.冗余连接
    a.问题描述
        LeetCode 684 - 找出使树变成图的边。
    b.并查集解法
        依次加边,找到第一条使图成环的边。

04.省份数量
    a.问题描述
        LeetCode 547 - 统计连通分量个数。
    b.DFS或并查集解法
        遍历所有节点,统计连通分量。

7 数学与贪心

7.1 数论基础

01.质数判定
    a.试除法
        ---
        public boolean isPrime(int n) {
            if (n < 2) return false;
            for (int i = 2; i <= n / i; i++) {
                if (n % i == 0) return false;
            }
            return true;
        }
        ---
    b.埃氏筛法
        ---
        public List<Integer> sieve(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 = i + i; j <= n; j += i) {
                        isPrime[j] = false;
                    }
                }
            }
            return primes;
        }
        ---

02.最大公约数
    a.欧几里得算法
        ---
        public int gcd(int a, int b) {
            return b == 0 ? a : gcd(b, a % b);
        }
        ---
    b.最小公倍数
        ---
        public int lcm(int a, int b) {
            return a / gcd(a, b) * b;
        }
        ---

03.快速幂
    a.递归实现
        ---
        public long quickPow(long a, long b, long mod) {
            if (b == 0) return 1;
            long half = quickPow(a, b / 2, mod);
            long res = half * half % mod;
            if (b % 2 == 1) res = res * a % mod;
            return res;
        }
        ---
    b.迭代实现
        ---
        public long quickPow(long a, long b, long mod) {
            long res = 1;
            while (b > 0) {
                if ((b & 1) == 1) res = res * a % mod;
                a = a * a % mod;
                b >>= 1;
            }
            return res;
        }
        ---

04.组合数
    a.递推公式
        C(n,m) = C(n-1,m-1) + C(n-1,m)
    b.预处理
        ---
        int N = 2010;
        long[][] C = new long[N][N];

        public void init() {
            for (int i = 0; i < N; i++) {
                for (int j = 0; j <= i; j++) {
                    if (j == 0) C[i][j] = 1;
                    else C[i][j] = C[i-1][j-1] + C[i-1][j];
                }
            }
        }
        ---

7.2 质数与约数

01.质数判定
    a.试除法
        ---
        public boolean isPrime(int n) {
            if (n < 2) return false;
            for (int i = 2; i <= n / i; i++) {
                if (n % i == 0) return false;
            }
            return true;
        }
        ---
    b.分解质因数
        ---
        public List<int[]> primeFactors(int n) {
            List<int[]> factors = new ArrayList<>();
            for (int i = 2; i <= n / i; i++) {
                if (n % i == 0) {
                    int count = 0;
                    while (n % i == 0) {
                        n /= i;
                        count++;
                    }
                    factors.add(new int[]{i, count});
                }
            }
            if (n > 1) factors.add(new int[]{n, 1});
            return factors;
        }
        ---

02.筛质数
    a.埃氏筛
        ---
        public List<Integer> sieveOfEratosthenes(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 (long j = (long)i * i; j <= n; j += i) {
                        isPrime[(int)j] = false;
                    }
                }
            }
            return primes;
        }
        ---
    b.线性筛
        ---
        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;
        }
        ---

03.约数
    a.求所有约数
        ---
        public List<Integer> getDivisors(int n) {
            List<Integer> divisors = new ArrayList<>();
            for (int i = 1; i <= n / i; i++) {
                if (n % i == 0) {
                    divisors.add(i);
                    if (i != n / i) divisors.add(n / i);
                }
            }
            Collections.sort(divisors);
            return divisors;
        }
        ---
    b.约数个数
        若n = p1^c1 * p2^c2 * ... * pk^ck,则约数个数为(c1+1)*(c2+1)*...*(ck+1)。

7.3 快速幂

01.快速幂原理
    a.基本思想
        利用二进制表示,将幂运算的时间复杂度从O(n)降到O(log n)。
    b.递推公式
        a^n = (a^(n/2))^2,如果n是奇数则再乘以a。

02.快速幂实现
    a.递归版本
        ---
        public long quickPow(long a, long b, long mod) {
            if (b == 0) return 1;
            long half = quickPow(a, b / 2, mod);
            long res = half * half % mod;
            if (b % 2 == 1) res = res * a % mod;
            return res;
        }
        ---
    b.迭代版本
        ---
        public long quickPow(long a, long b, long mod) {
            long res = 1;
            while (b > 0) {
                if ((b & 1) == 1) res = res * a % mod;
                a = a * a % mod;
                b >>= 1;
            }
            return res;
        }
        ---
    c.Go实现
        ---
        func quickPow(a, b, mod int64) int64 {
            res := int64(1)
            for b > 0 {
                if b&1 == 1 {
                    res = res * a % mod
                }
                a = a * a % mod
                b >>= 1
            }
            return res
        }
        ---

03.快速幂应用
    a.求幂
        LeetCode 50 - 实现pow(x, n)。
    b.超级次方
        LeetCode 372 - 计算a^b mod 1337,其中b是数组表示的大数。
    c.模运算
        AcWing 875快速幂求逆元 - 利用费马小定理求逆元。

7.4 组合计数

01.组合数计算
    a.递推公式
        C(n,m) = C(n-1,m-1) + C(n-1,m)
    b.预处理
        ---
        int N = 2010;
        long[][] C = new long[N][N];

        public void init() {
            for (int i = 0; i < N; i++) {
                for (int j = 0; j <= i; j++) {
                    if (j == 0) C[i][j] = 1;
                    else C[i][j] = C[i-1][j-1] + C[i-1][j];
                }
            }
        }
        ---
    c.公式法
        C(n,m) = n! / (m! * (n-m)!)

02.Lucas定理
    a.定理
        C(n,m) mod p = C(n/p, m/p) * C(n%p, m%p) mod p,其中p是质数。
    b.实现
        ---
        public long lucas(long n, long m, long p) {
            if (m == 0) return 1;
            return C(n % p, m % p, p) * lucas(n / p, m / p, p) % p;
        }

        private long C(long n, long m, long p) {
            if (m > n) return 0;
            long res = 1;
            for (long i = 1, j = n; i <= m; i++, j--) {
                res = res * j % p;
                res = res * quickPow(i, p - 2, p) % p;
            }
            return res;
        }
        ---

03.卡特兰数
    a.定义
        Cat(n) = C(2n, n) / (n + 1)
    b.递推公式
        Cat(n) = Cat(0)*Cat(n-1) + Cat(1)*Cat(n-2) + ... + Cat(n-1)*Cat(0)
    c.应用
        a.括号匹配
            n对括号的合法组合数。
        b.出栈序列
            n个元素的出栈序列数。
        c.二叉树
            n个节点的二叉树数量。

7.5 贪心算法

01.贪心算法概述
    a.基本思想
        每一步都选择当前状态下的最优解,期望得到全局最优解。
    b.适用条件
        问题具有贪心选择性质和最优子结构性质。
    c.证明方法
        交换论证法、反证法、数学归纳法。

02.区间问题
    a.区间选点
        ---
        // 给定n个区间,选择最少的点使得每个区间至少包含一个点
        public int intervalCover(int[][] intervals) {
            Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
            int count = 0, end = Integer.MIN_VALUE;

            for (int[] interval : intervals) {
                if (interval[0] > end) {
                    count++;
                    end = interval[1];
                }
            }
            return count;
        }
        ---
    b.最大不相交区间数
        ---
        public int maxDisjointIntervals(int[][] intervals) {
            Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
            int count = 0, end = Integer.MIN_VALUE;

            for (int[] interval : intervals) {
                if (interval[0] >= end) {
                    count++;
                    end = interval[1];
                }
            }
            return count;
        }
        ---

03.Huffman树
    a.问题描述
        给定n个权值,构造Huffman树使得带权路径长度最小。
    b.贪心策略
        每次选择两个最小的节点合并。
    c.Java实现
        ---
        public long huffman(int[] weights) {
            PriorityQueue<Long> pq = new PriorityQueue<>();
            for (int w : weights) {
                pq.offer((long) w);
            }

            long cost = 0;
            while (pq.size() > 1) {
                long a = pq.poll();
                long b = pq.poll();
                long sum = a + b;
                cost += sum;
                pq.offer(sum);
            }
            return cost;
        }
        ---

04.排序不等式
    a.问题
        给定两个序列,如何配对使得乘积和最大或最小。
    b.贪心策略
        最大:同序配对,最小:逆序配对。
    c.证明
        交换论证法。

05.经典贪心题目
    a.跳跃游戏
        LeetCode 55 - 判断能否跳到最后一个位置。
    b.分发饼干
        LeetCode 455 - 用最少的饼干满足最多的孩子。
    c.买卖股票的最佳时机
        LeetCode 121 - 一次交易获得最大利润。

7.6 高精度运算

01.高精度加法
    a.实现
        ---
        public String addStrings(String num1, String num2) {
            StringBuilder sb = new StringBuilder();
            int i = num1.length() - 1;
            int j = num2.length() - 1;
            int carry = 0;

            while (i >= 0 || j >= 0 || carry > 0) {
                int n1 = i >= 0 ? num1.charAt(i) - '0' : 0;
                int n2 = j >= 0 ? num2.charAt(j) - '0' : 0;
                int sum = n1 + n2 + carry;
                sb.append(sum % 10);
                carry = sum / 10;
                i--;
                j--;
            }
            return sb.reverse().toString();
        }
        ---

02.高精度减法
    a.实现
        ---
        public String subtract(String num1, String num2) {
            // 确保num1 >= num2
            if (compare(num1, num2) < 0) {
                return "-" + subtract(num2, num1);
            }

            StringBuilder sb = new StringBuilder();
            int i = num1.length() - 1;
            int j = num2.length() - 1;
            int borrow = 0;

            while (i >= 0) {
                int n1 = num1.charAt(i) - '0';
                int n2 = j >= 0 ? num2.charAt(j) - '0' : 0;
                int diff = n1 - n2 - borrow;
                if (diff < 0) {
                    diff += 10;
                    borrow = 1;
                } else {
                    borrow = 0;
                }
                sb.append(diff);
                i--;
                j--;
            }

            // 去除前导零
            while (sb.length() > 1 && sb.charAt(sb.length() - 1) == '0') {
                sb.deleteCharAt(sb.length() - 1);
            }

            return sb.reverse().toString();
        }
        ---

03.高精度乘法
    a.大数乘小数
        ---
        public String multiply(String num, int x) {
            StringBuilder sb = new StringBuilder();
            int carry = 0;

            for (int i = num.length() - 1; i >= 0; i--) {
                int digit = num.charAt(i) - '0';
                int prod = digit * x + carry;
                sb.append(prod % 10);
                carry = prod / 10;
            }

            while (carry > 0) {
                sb.append(carry % 10);
                carry /= 10;
            }

            return sb.reverse().toString();
        }
        ---
    b.大数乘大数
        LeetCode 43 - 字符串相乘。

04.高精度除法
    a.大数除小数
        ---
        public String divide(String num, int x) {
            StringBuilder sb = new StringBuilder();
            int remainder = 0;

            for (int i = 0; i < num.length(); i++) {
                int digit = num.charAt(i) - '0';
                int current = remainder * 10 + digit;
                sb.append(current / x);
                remainder = current % x;
            }

            // 去除前导零
            int start = 0;
            while (start < sb.length() - 1 && sb.charAt(start) == '0') {
                start++;
            }

            return sb.substring(start);
        }
        ---

7.7 综合实战

01.数学综合题
    a.丑数
        LeetCode 264 - 只包含质因子2、3、5的数。
        ---
        public int nthUglyNumber(int n) {
            int[] dp = new int[n];
            dp[0] = 1;
            int p2 = 0, p3 = 0, p5 = 0;

            for (int i = 1; i < n; i++) {
                int num2 = dp[p2] * 2;
                int num3 = dp[p3] * 3;
                int num5 = dp[p5] * 5;
                dp[i] = Math.min(num2, Math.min(num3, num5));

                if (dp[i] == num2) p2++;
                if (dp[i] == num3) p3++;
                if (dp[i] == num5) p5++;
            }
            return dp[n-1];
        }
        ---
    b.完全平方数
        LeetCode 279 - 和为n的完全平方数的最少数量。

02.贪心综合题
    a.跳跃游戏II
        LeetCode 45 - 跳到最后一个位置的最小跳跃次数。
        ---
        public int jump(int[] nums) {
            int jumps = 0;
            int currentEnd = 0;
            int farthest = 0;

            for (int i = 0; i < nums.length - 1; i++) {
                farthest = Math.max(farthest, i + nums[i]);
                if (i == currentEnd) {
                    jumps++;
                    currentEnd = farthest;
                }
            }
            return jumps;
        }
        ---
    b.加油站
        LeetCode 134 - 环形路线中找到起始加油站。

03.AcWing综合题
    a.AcWing 104货仓选址
        在数轴上选择一个点,使得所有点到该点的距离之和最小。
    b.AcWing 125耍杂技的牛
        贪心排序问题,按w+s排序。

04.总结
    a.数学问题
        注重公式推导和数学性质的应用。
    b.贪心问题
        关键是找到贪心策略并证明其正确性。
    c.综合运用
        实际问题往往需要多种算法技巧的结合。