1 介绍

1.1 数据结构概述

01.定义与重要性
    a.什么是数据结构
        数据结构是计算机存储、组织数据的方式,是数据元素相互之间存在的一种或多种特定关系的集合。
    b.为什么要学习数据结构
        a.提升编程能力
            掌握数据结构能够帮助开发者编写更高效、更优雅的代码,解决复杂问题。
        b.面试必备
            几乎所有大厂技术面试都会考察数据结构与算法能力。
        c.系统设计基础
            数据结构是系统架构设计的基石,影响系统性能和可扩展性。

02.数据结构的作用
    a.提高程序效率
        a.时间效率
            合适的数据结构能够显著降低算法时间复杂度,如使用哈希表实现O(1)查找。
        b.空间效率
            优化内存使用,减少空间浪费,如使用链表实现动态扩展。
    b.简化代码逻辑
        使用合适的数据结构可以让代码更简洁、更易维护。
    c.解决实际问题
        a.缓存系统
            使用LRU缓存结构优化数据访问性能。
        b.数据库索引
            使用B+树实现高效的数据检索。
        c.网络路由
            使用图结构实现最短路径算法。

03.数据结构与算法的关系
    a.相辅相成
        数据结构是算法的基础,算法是数据结构的应用,两者密不可分。
    b.程序设计公式
        程序 = 数据结构 + 算法,这是计算机科学的经典公式。
    c.学习顺序
        先掌握基本数据结构,再学习算法设计,最后进行综合应用。

1.2 时间与空间复杂度

01.时间复杂度
    a.定义
        算法执行时间随数据规模增长的变化趋势,用大O表示法描述。
    b.常见时间复杂度
        a.O(1)常数时间
            执行时间不随数据规模变化,如数组随机访问、哈希表查找。
        b.O(log n)对数时间
            每次操作将问题规模减半,如二分查找、平衡树操作。
        c.O(n)线性时间
            需要遍历所有元素一次,如线性查找、数组遍历。
        d.O(n log n)线性对数时间
            高效排序算法的时间复杂度,如快速排序、归并排序、堆排序。
        e.O(n²)平方时间
            双层循环遍历,如冒泡排序、选择排序、插入排序。
        f.O(2ⁿ)指数时间
            递归求解所有子集,如斐波那契数列的朴素递归实现。
    c.复杂度比较
        O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ) < O(n!)

02.空间复杂度
    a.定义
        算法执行过程中临时占用的存储空间大小,不包括输入数据本身占用的空间。
    b.常见空间复杂度
        a.O(1)常数空间
            只使用固定数量的额外变量,如原地排序算法。
        b.O(n)线性空间
            需要额外的数组或列表存储中间结果,如归并排序。
        c.O(log n)对数空间
            递归调用栈的深度,如二分查找的递归实现。
    c.空间换时间
        通过增加空间使用来降低时间复杂度,如动态规划的记忆化搜索。

03.复杂度分析方法
    a.最坏情况分析
        分析算法在最不利情况下的性能表现,是最常用的分析方法。
    b.平均情况分析
        考虑所有可能输入的平均性能,更贴近实际使用场景。
    c.最好情况分析
        算法在最理想情况下的性能,参考价值较小。
    d.代码示例
        a.Java实现
            ---
            // 时间复杂度分析示例
            public class ComplexityDemo {
                // O(1) - 常数时间
                public int getFirst(int[] arr) {
                    return arr[0];
                }

                // O(n) - 线性时间
                public int sum(int[] arr) {
                    int total = 0;
                    for (int num : arr) {
                        total += num;
                    }
                    return total;
                }

                // O(log n) - 对数时间
                public int binarySearch(int[] arr, int target) {
                    int left = 0, right = arr.length - 1;
                    while (left <= right) {
                        int mid = left + (right - left) / 2;
                        if (arr[mid] == target) return mid;
                        else if (arr[mid] < target) left = mid + 1;
                        else right = mid - 1;
                    }
                    return -1;
                }

                // O(n²) - 平方时间
                public void bubbleSort(int[] arr) {
                    for (int i = 0; i < arr.length; i++) {
                        for (int j = 0; j < arr.length - i - 1; j++) {
                            if (arr[j] > arr[j + 1]) {
                                int temp = arr[j];
                                arr[j] = arr[j + 1];
                                arr[j + 1] = temp;
                            }
                        }
                    }
                }
            }
            ---
        b.Go实现
            ---
            // 时间复杂度分析示例
            package main

            // O(1) - 常数时间
            func getFirst(arr []int) int {
                return arr[0]
            }

            // O(n) - 线性时间
            func sum(arr []int) int {
                total := 0
                for _, num := range arr {
                    total += num
                }
                return total
            }

            // O(log n) - 对数时间
            func binarySearch(arr []int, target int) int {
                left, right := 0, len(arr)-1
                for left <= right {
                    mid := left + (right-left)/2
                    if arr[mid] == target {
                        return mid
                    } else if arr[mid] < target {
                        left = mid + 1
                    } else {
                        right = mid - 1
                    }
                }
                return -1
            }

            // O(n²) - 平方时间
            func bubbleSort(arr []int) {
                n := len(arr)
                for i := 0; i < n; i++ {
                    for j := 0; j < n-i-1; j++ {
                        if arr[j] > arr[j+1] {
                            arr[j], arr[j+1] = arr[j+1], arr[j]
                        }
                    }
                }
            }
            ---

1.3 数据结构分类

01.按逻辑结构分类
    a.线性结构
        a.定义
            数据元素之间存在一对一的线性关系,每个元素最多只有一个前驱和一个后继。
        b.典型代表
            数组、链表、栈、队列、字符串等。
    b.树形结构
        a.定义
            数据元素之间存在一对多的层次关系,每个元素可以有多个后继但只有一个前驱。
        b.典型代表
            二叉树、二叉搜索树、AVL树、红黑树、B树、堆等。
    c.图形结构
        a.定义
            数据元素之间存在多对多的任意关系,元素之间可以有任意连接。
        b.典型代表
            有向图、无向图、加权图、网络流图等。
    d.集合结构
        数据元素之间除了同属一个集合外,没有其他关系。

02.按存储结构分类
    a.顺序存储
        a.特点
            用一组连续的存储单元依次存储数据元素,逻辑上相邻的元素在物理位置上也相邻。
        b.优点
            支持随机访问,访问速度快,空间利用率高。
        c.缺点
            插入删除操作需要移动大量元素,不适合频繁的插入删除操作。
        d.典型代表
            数组、ArrayList、Vector等。
    b.链式存储
        a.特点
            用指针将分散的存储单元链接起来,逻辑上相邻的元素在物理位置上可以不相邻。
        b.优点
            插入删除操作方便,不需要移动元素,动态分配空间。
        c.缺点
            不支持随机访问,需要额外空间存储指针,空间利用率较低。
        d.典型代表
            单链表、双向链表、循环链表、LinkedList等。
    c.索引存储
        在存储元素的同时,建立附加的索引表,通过索引快速定位元素。
    d.散列存储
        通过哈希函数计算元素的存储位置,实现快速查找。

03.按操作特性分类
    a.线性表
        支持顺序访问和随机访问,可以在任意位置插入删除。
    b.栈
        后进先出LIFO,只能在栈顶进行插入删除操作。
    c.队列
        先进先出FIFO,在队尾插入,在队头删除。
    d.树
        支持层次遍历、前序遍历、中序遍历、后序遍历等操作。
    e.图
        支持深度优先遍历DFS、广度优先遍历BFS等操作。

1.4 学习路线

01.基础阶段
    a.第一步:掌握基本概念
        a.学习内容
            理解数据结构的定义、分类、时间空间复杂度分析方法。
        b.学习时间
            建议1-2周,打好理论基础。
        c.学习资源
            教材、在线课程、技术博客等。
    b.第二步:学习线性结构
        a.数组与动态数组
            掌握数组的基本操作、ArrayList和Vector的使用。
        b.链表
            实现单链表、双向链表、循环链表,理解链表的优缺点。
        c.栈与队列
            实现栈和队列,理解其应用场景。
        d.学习时间
            建议2-3周,每种数据结构都要手写实现。

02.进阶阶段
    a.第三步:学习树形结构
        a.二叉树基础
            掌握二叉树的遍历、构建、查找等基本操作。
        b.二叉搜索树
            理解BST的性质,实现插入、删除、查找操作。
        c.平衡树
            学习AVL树、红黑树的原理和实现。
        d.堆结构
            掌握堆的性质,实现堆排序和优先队列。
        e.学习时间
            建议3-4周,树是重点也是难点。
    b.第四步:学习哈希表
        a.哈希函数设计
            理解常见的哈希函数和冲突解决方法。
        b.HashMap实现
            深入理解Java HashMap和Go Map的实现原理。
        c.应用场景
            掌握哈希表在实际问题中的应用。
        d.学习时间
            建议1-2周。

03.高级阶段
    a.第五步:学习图结构
        a.图的表示
            掌握邻接矩阵和邻接表两种表示方法。
        b.图的遍历
            实现DFS和BFS算法。
        c.最短路算法
            学习Dijkstra、Floyd、Bellman-Ford算法。
        d.最小生成树
            掌握Prim和Kruskal算法。
        e.学习时间
            建议3-4周。
    b.第六步:高级数据结构
        a.并查集
            理解路径压缩和按秩合并优化。
        b.线段树与树状数组
            掌握区间查询和更新操作。
        c.字典树Trie
            实现前缀匹配和字符串检索。
        d.学习时间
            建议2-3周。

04.实战阶段
    a.刷题训练
        a.LeetCode
            按标签刷题,从简单到困难,每天2-3题。
        b.AcWing
            系统学习算法课程,完成配套练习。
        c.目标
            累计完成300+题目,覆盖所有数据结构类型。
    b.项目实践
        a.实现常用数据结构库
            用Java和Go分别实现一套完整的数据结构库。
        b.解决实际问题
            将数据结构应用到实际项目中,如缓存系统、搜索引擎等。
    c.持续学习
        关注最新的数据结构研究成果,学习工业界的最佳实践。

1.5 应用场景

01.系统设计场景
    a.缓存系统
        a.LRU缓存
            使用双向链表+哈希表实现O(1)时间复杂度的缓存淘汰策略。
        b.LFU缓存
            使用多个链表+哈希表实现基于访问频率的缓存淘汰。
        c.代码示例
            ---
            // Java实现LRU缓存
            class LRUCache {
                class Node {
                    int key, value;
                    Node prev, next;
                    Node(int k, int v) { key = k; value = v; }
                }

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

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

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

                public void put(int key, int value) {
                    if (map.containsKey(key)) {
                        remove(map.get(key));
                    }
                    if (map.size() == capacity) {
                        remove(tail.prev);
                    }
                    insert(new Node(key, value));
                }

                private void remove(Node node) {
                    map.remove(node.key);
                    node.prev.next = node.next;
                    node.next.prev = node.prev;
                }

                private void insert(Node node) {
                    map.put(node.key, node);
                    node.next = head.next;
                    node.prev = head;
                    head.next.prev = node;
                    head.next = node;
                }
            }
            ---
    b.数据库索引
        a.B+树索引
            MySQL InnoDB引擎使用B+树实现索引,支持范围查询和顺序访问。
        b.哈希索引
            Memory引擎使用哈希表实现索引,支持等值查询。
    c.消息队列
        使用队列结构实现生产者消费者模型,如RabbitMQ、Kafka等。

02.算法竞赛场景
    a.动态规划
        使用数组存储中间状态,避免重复计算。
    b.图论算法
        使用邻接表存储图结构,实现最短路、最小生成树等算法。
    c.字符串匹配
        使用Trie树实现多模式匹配,使用KMP算法实现单模式匹配。
    d.区间查询
        使用线段树或树状数组实现高效的区间查询和更新。

03.工程实践场景
    a.Web开发
        a.Session管理
            使用哈希表存储用户会话信息。
        b.路由匹配
            使用Trie树实现URL路由快速匹配。
        c.限流算法
            使用滑动窗口(队列)实现请求限流。
    b.大数据处理
        a.布隆过滤器
            使用位数组判断元素是否存在,节省空间。
        b.跳表
            Redis使用跳表实现有序集合,支持快速查找和范围查询。
        c.一致性哈希
            分布式系统使用一致性哈希实现负载均衡。
    c.人工智能
        a.决策树
            机器学习中使用树结构表示决策规则。
        b.图神经网络
            使用图结构表示实体之间的关系。

04.面试常见场景
    a.设计题
        a.设计Twitter
            使用哈希表+堆实现关注列表和时间线。
        b.设计搜索引擎
            使用倒排索引(哈希表+链表)实现关键词检索。
    b.算法题
        a.两数之和
            使用哈希表实现O(n)时间复杂度。
        b.合并K个有序链表
            使用最小堆实现高效合并。
        c.LRU缓存
            使用双向链表+哈希表实现。

2 线性表

2.1 数组

01.数组基础
    a.定义
        数组是一种线性数据结构,使用连续的内存空间存储相同类型的元素,通过索引访问。
    b.特点
        a.连续存储
            数组元素在内存中连续存放,物理地址相邻。
        b.随机访问
            通过索引可以在O(1)时间内访问任意元素。
        c.固定长度
            数组创建后长度固定,不能动态扩展(Java和Go的原生数组)。
    c.时间复杂度
        访问O(1)、查找O(n)、插入O(n)、删除O(n)。

02.数组的基本操作
    a.创建与初始化
        a.Java实现
            ---
            // 数组创建与初始化
            int[] arr1 = new int[5];                    // 创建长度为5的数组
            int[] arr2 = {1, 2, 3, 4, 5};              // 直接初始化
            int[] arr3 = new int[]{1, 2, 3, 4, 5};    // 完整写法

            // 二维数组
            int[][] matrix = new int[3][4];             // 3行4列
            int[][] matrix2 = {{1,2}, {3,4}, {5,6}};   // 直接初始化
            ---
        b.Go实现
            ---
            // 数组创建与初始化
            var arr1 [5]int                    // 创建长度为5的数组
            arr2 := [5]int{1, 2, 3, 4, 5}     // 直接初始化
            arr3 := [...]int{1, 2, 3, 4, 5}   // 自动推断长度

            // 二维数组
            var matrix [3][4]int
            matrix2 := [3][2]int{{1,2}, {3,4}, {5,6}}
            ---
    b.访问与修改
        a.功能说明
            通过索引访问和修改数组元素,索引从0开始。
        b.代码示例
            ---
            // Java
            int[] arr = {10, 20, 30, 40, 50};
            int first = arr[0];        // 访问第一个元素:10
            arr[2] = 100;              // 修改第三个元素为100
            int length = arr.length;   // 获取数组长度:5

            // Go
            arr := [5]int{10, 20, 30, 40, 50}
            first := arr[0]            // 访问第一个元素:10
            arr[2] = 100               // 修改第三个元素为100
            length := len(arr)         // 获取数组长度:5
            ---
    c.遍历数组
        a.Java实现
            ---
            int[] arr = {1, 2, 3, 4, 5};

            // 方式1:for循环
            for (int i = 0; i < arr.length; i++) {
                System.out.println(arr[i]);
            }

            // 方式2:增强for循环
            for (int num : arr) {
                System.out.println(num);
            }

            // 方式3:Stream API(Java 8+)
            Arrays.stream(arr).forEach(System.out::println);
            ---
        b.Go实现
            ---
            arr := [5]int{1, 2, 3, 4, 5}

            // 方式1:for循环
            for i := 0; i < len(arr); i++ {
                fmt.Println(arr[i])
            }

            // 方式2:range遍历
            for index, value := range arr {
                fmt.Printf("arr[%d] = %d\n", index, value)
            }

            // 只要值不要索引
            for _, value := range arr {
                fmt.Println(value)
            }
            ---

03.数组的优缺点
    a.优点
        a.随机访问快
            通过索引可以在O(1)时间内访问任意元素。
        b.内存连续
            缓存友好,CPU缓存命中率高,访问效率高。
        c.实现简单
            语言原生支持,使用方便。
    b.缺点
        a.长度固定
            创建后不能动态扩展,需要提前确定大小。
        b.插入删除慢
            需要移动大量元素,时间复杂度O(n)。
        c.空间浪费
            如果预分配空间过大,会造成内存浪费。

04.常见应用
    a.存储固定大小的数据
        如月份、星期、扑克牌等。
    b.实现其他数据结构
        栈、队列、堆等都可以用数组实现。
    c.矩阵运算
        二维数组用于表示矩阵,进行数学计算。
    d.查找表
        用数组存储映射关系,如ASCII码表。

2.2 动态数组

01.动态数组概述
    a.定义
        动态数组是可以自动扩容的数组,当容量不足时会自动分配更大的空间。
    b.Java中的ArrayList
        Java标准库提供的动态数组实现,基于数组实现,支持自动扩容。
    c.Go中的Slice
        Go语言的切片是对数组的抽象,提供动态长度和容量管理。

02.ArrayList实现原理
    a.底层结构
        a.数组存储
            使用Object数组存储元素,默认初始容量为10。
        b.扩容机制
            当容量不足时,扩容为原来的1.5倍(newCapacity = oldCapacity + oldCapacity >> 1)。
        c.时间复杂度
            访问O(1)、尾部插入O(1)摊销、中间插入O(n)、删除O(n)。
    b.核心方法
        a.Java实现
            ---
            import java.util.ArrayList;
            import java.util.List;

            public class ArrayListDemo {
                public static void main(String[] args) {
                    // 创建ArrayList
                    List<Integer> list = new ArrayList<>();

                    // 添加元素
                    list.add(10);           // 尾部添加
                    list.add(20);
                    list.add(1, 15);        // 指定位置插入

                    // 访问元素
                    int first = list.get(0);        // 获取第一个元素
                    int size = list.size();         // 获取大小

                    // 修改元素
                    list.set(0, 100);               // 修改第一个元素

                    // 删除元素
                    list.remove(1);                 // 删除索引1的元素
                    list.remove(Integer.valueOf(20)); // 删除值为20的元素

                    // 查找元素
                    boolean contains = list.contains(15);
                    int index = list.indexOf(15);

                    // 遍历
                    for (int num : list) {
                        System.out.println(num);
                    }

                    // 清空
                    list.clear();
                }
            }
            ---
    c.手写ArrayList
        a.功能说明
            实现一个简化版的动态数组,包含基本的增删改查功能。
        b.代码示例
            ---
            public class MyArrayList<E> {
                private Object[] elements;
                private int size;
                private static final int DEFAULT_CAPACITY = 10;

                public MyArrayList() {
                    elements = new Object[DEFAULT_CAPACITY];
                }

                public void add(E element) {
                    ensureCapacity();
                    elements[size++] = element;
                }

                public void add(int index, E element) {
                    if (index < 0 || index > size) {
                        throw new IndexOutOfBoundsException();
                    }
                    ensureCapacity();
                    System.arraycopy(elements, index, elements, index + 1, size - index);
                    elements[index] = element;
                    size++;
                }

                @SuppressWarnings("unchecked")
                public E get(int index) {
                    if (index < 0 || index >= size) {
                        throw new IndexOutOfBoundsException();
                    }
                    return (E) elements[index];
                }

                public E remove(int index) {
                    if (index < 0 || index >= size) {
                        throw new IndexOutOfBoundsException();
                    }
                    E oldValue = get(index);
                    int numMoved = size - index - 1;
                    if (numMoved > 0) {
                        System.arraycopy(elements, index + 1, elements, index, numMoved);
                    }
                    elements[--size] = null;
                    return oldValue;
                }

                public int size() {
                    return size;
                }

                private void ensureCapacity() {
                    if (size == elements.length) {
                        int newCapacity = elements.length + (elements.length >> 1);
                        elements = Arrays.copyOf(elements, newCapacity);
                    }
                }
            }
            ---

03.Go Slice详解
    a.Slice结构
        a.底层实现
            Slice是对数组的引用,包含指针、长度和容量三个字段。
        b.扩容机制
            当容量不足时,如果当前容量小于1024,扩容为2倍,否则扩容为1.25倍。
    b.基本操作
        a.Go实现
            ---
            package main

            import "fmt"

            func main() {
                // 创建slice
                var s1 []int                    // nil slice
                s2 := []int{}                   // 空slice
                s3 := []int{1, 2, 3, 4, 5}     // 初始化
                s4 := make([]int, 5)            // 长度为5
                s5 := make([]int, 5, 10)        // 长度5,容量10

                // 添加元素
                s3 = append(s3, 6)              // 尾部添加
                s3 = append(s3, 7, 8, 9)        // 添加多个

                // 访问元素
                first := s3[0]                  // 访问第一个元素
                length := len(s3)               // 获取长度
                capacity := cap(s3)             // 获取容量

                // 切片操作
                sub := s3[1:4]                  // 获取子切片[1,4)
                sub2 := s3[:3]                  // 从开始到索引3
                sub3 := s3[2:]                  // 从索引2到结束

                // 复制slice
                dest := make([]int, len(s3))
                copy(dest, s3)

                // 删除元素(删除索引i的元素)
                i := 2
                s3 = append(s3[:i], s3[i+1:]...)

                // 遍历
                for index, value := range s3 {
                    fmt.Printf("s3[%d] = %d\n", index, value)
                }
            }
            ---
    c.Slice陷阱
        a.共享底层数组
            多个slice可能共享同一个底层数组,修改一个会影响其他。
        b.扩容后的独立性
            扩容后会创建新数组,原slice和新slice不再共享底层数组。
        c.代码示例
            ---
            // Slice陷阱示例
            func sliceTrap() {
                s1 := []int{1, 2, 3, 4, 5}
                s2 := s1[1:3]           // s2 = [2, 3]
                s2[0] = 100             // 修改s2会影响s1
                fmt.Println(s1)         // [1, 100, 3, 4, 5]

                // 扩容后独立
                s2 = append(s2, 6, 7, 8, 9)  // 触发扩容
                s2[0] = 200             // 不再影响s1
                fmt.Println(s1)         // [1, 100, 3, 4, 5]
                fmt.Println(s2)         // [200, 3, 6, 7, 8, 9]
            }
            ---

04.性能对比
    a.ArrayList vs LinkedList
        ArrayList随机访问快,LinkedList插入删除快。
    b.扩容开销
        频繁扩容会影响性能,建议预估容量使用带容量的构造函数。
    c.内存占用
        ArrayList比LinkedList节省内存,因为不需要存储指针。

2.3 链表基础

01.链表概述
    a.定义
        链表是一种物理存储单元上非连续、非顺序的存储结构,通过指针将各个节点链接在一起。
    b.基本组成
        a.节点Node
            包含数据域和指针域,数据域存储元素值,指针域存储下一个节点的地址。
        b.头节点
            链表的第一个节点,可以是哨兵节点(不存储数据)或数据节点。
        c.尾节点
            链表的最后一个节点,指针域为null。
    c.链表分类
        单链表、双向链表、循环链表。

02.链表的特点
    a.优点
        a.动态扩展
            不需要预先分配空间,可以动态增加或删除节点。
        b.插入删除快
            只需修改指针,时间复杂度O(1),不需要移动元素。
        c.内存利用率高
            不会浪费内存空间,按需分配。
    b.缺点
        a.不支持随机访问
            必须从头节点开始遍历,访问第i个元素时间复杂度O(n)。
        b.额外空间开销
            每个节点需要额外的指针空间。
        c.缓存不友好
            节点在内存中不连续,CPU缓存命中率低。

03.链表与数组对比
    a.时间复杂度对比
        a.访问
            数组O(1),链表O(n)。
        b.查找
            数组O(n),链表O(n)。
        c.插入
            数组O(n),链表O(1)(已知位置)。
        d.删除
            数组O(n),链表O(1)(已知位置)。
    b.空间复杂度对比
        数组需要连续空间,链表可以使用分散空间。
    c.使用场景
        a.数组适用场景
            需要频繁随机访问,元素数量固定或变化不大。
        b.链表适用场景
            需要频繁插入删除,不需要随机访问,元素数量动态变化。

04.节点定义
    a.Java实现
        a.功能说明
            定义单链表节点类,包含数据域和指针域。
        b.代码示例
            ---
            // 单链表节点定义
            class ListNode {
                int val;
                ListNode next;

                ListNode() {}

                ListNode(int val) {
                    this.val = val;
                }

                ListNode(int val, ListNode next) {
                    this.val = val;
                    this.next = next;
                }
            }

            // 泛型节点定义
            class Node<E> {
                E data;
                Node<E> next;

                Node(E data) {
                    this.data = data;
                }
            }
            ---
    b.Go实现
        a.功能说明
            Go语言中使用结构体定义链表节点。
        b.代码示例
            ---
            // 单链表节点定义
            type ListNode struct {
                Val  int
                Next *ListNode
            }

            // 创建节点
            func NewListNode(val int) *ListNode {
                return &ListNode{Val: val}
            }

            // 泛型节点定义(Go 1.18+)
            type Node[T any] struct {
                Data T
                Next *Node[T]
            }

            func NewNode[T any](data T) *Node[T] {
                return &Node[T]{Data: data}
            }
            ---

2.4 单链表实现

01.单链表基本操作
    a.创建链表
        a.Java实现
            ---
            public class LinkedList {
                private ListNode head;  // 链表头节点,指向第一个元素
                private int size;       // 链表长度,记录节点数量

                // 构造函数:初始化空链表
                public LinkedList() {
                    head = null;  // 空链表头节点为null
                    size = 0;     // 初始长度为0
                }

                // 头插法创建链表:从数组头部开始,依次插入到链表头部
                // 时间复杂度:O(n),空间复杂度:O(1)
                // 结果:链表顺序与数组相反
                public void createByHead(int[] arr) {
                    for (int val : arr) {
                        ListNode node = new ListNode(val);  // 创建新节点
                        node.next = head;  // 新节点指向当前头节点
                        head = node;       // 更新头节点为新节点
                        size++;            // 长度加1
                    }
                }
                // 📊 执行示例:
                // 输入数组:[1, 2, 3]
                // 执行过程:
                //   初始:head -> null
                //   插入1:head -> 1 -> null
                //   插入2:head -> 2 -> 1 -> null
                //   插入3:head -> 3 -> 2 -> 1 -> null
                // 最终结果:3 -> 2 -> 1 -> null(顺序反转)

                // 尾插法创建链表:从数组头部开始,依次插入到链表尾部
                // 时间复杂度:O(n),空间复杂度:O(1)
                // 结果:链表顺序与数组相同
                public void createByTail(int[] arr) {
                    ListNode tail = null;  // 尾指针,指向链表最后一个节点
                    for (int val : arr) {
                        ListNode node = new ListNode(val);  // 创建新节点
                        if (head == null) {
                            head = node;  // 第一个节点既是头也是尾
                        } else {
                            tail.next = node;  // 将新节点连接到尾部
                        }
                        tail = node;  // 更新尾指针
                        size++;       // 长度加1
                    }
                }
                // 📊 执行示例:
                // 输入数组:[1, 2, 3]
                // 执行过程:
                //   初始:head -> null, tail -> null
                //   插入1:head -> 1 -> null, tail -> 1
                //   插入2:head -> 1 -> 2 -> null, tail -> 2
                //   插入3:head -> 1 -> 2 -> 3 -> null, tail -> 3
                // 最终结果:1 -> 2 -> 3 -> null(顺序保持)
            }
            ---

        b.图解说明
            📌 头插法可视化过程:
            数组 [1, 2, 3] 使用头插法创建链表

            步骤1:插入1
            head
              ↓
            [1] -> null

            步骤2:插入2
            head
              ↓
            [2] -> [1] -> null

            步骤3:插入3
            head
              ↓
            [3] -> [2] -> [1] -> null

            📌 尾插法可视化过程:
            数组 [1, 2, 3] 使用尾插法创建链表

            步骤1:插入1
            head/tail
                ↓
              [1] -> null

            步骤2:插入2
            head      tail
              ↓         ↓
            [1] -> [2] -> null

            步骤3:插入3
            head           tail
              ↓              ↓
            [1] -> [2] -> [3] -> null

        c.常见错误与陷阱
            ⚠️ 头插法常见错误:
            1. 忘记更新head指针
               错误:node.next = head; (缺少 head = node;)
               后果:新节点未连接到链表

            2. size计数错误
               错误:在循环外只执行一次size++
               后果:链表长度统计不准确

            ⚠️ 尾插法常见错误:
            1. 第一个节点未初始化tail
               错误:只设置head,不设置tail
               后果:第二个节点插入时tail为null,空指针异常

            2. 忘记更新tail指针
               错误:tail.next = node; (缺少 tail = node;)
               后果:所有节点都插入到第一个节点后面

            3. 空数组处理
               边界:arr为空数组时,head和tail都应为null
        d.Go实现
            ---
            type LinkedList struct {
                head *ListNode  // 链表头节点指针
                size int        // 链表长度
            }

            // NewLinkedList 创建新的空链表
            func NewLinkedList() *LinkedList {
                return &LinkedList{}  // Go的零值:head为nil,size为0
            }

            // CreateByHead 头插法创建链表
            // 时间复杂度:O(n),空间复杂度:O(1)
            func (l *LinkedList) CreateByHead(arr []int) {
                for _, val := range arr {  // 遍历数组元素
                    node := &ListNode{Val: val}  // 创建新节点(Go使用&取地址)
                    node.Next = l.head           // 新节点指向当前头节点
                    l.head = node                // 更新头节点
                    l.size++                     // 长度递增
                }
            }

            // CreateByTail 尾插法创建链表
            // 时间复杂度:O(n),空间复杂度:O(1)
            func (l *LinkedList) CreateByTail(arr []int) {
                var tail *ListNode  // 尾指针,Go的var声明默认为nil
                for _, val := range arr {
                    node := &ListNode{Val: val}  // 创建新节点
                    if l.head == nil {
                        l.head = node  // 第一个节点
                    } else {
                        tail.Next = node  // 连接到尾部
                    }
                    tail = node  // 更新尾指针
                    l.size++
                }
            }
            ---

            💡 Go语言特性说明:
            1. 使用&ListNode{}创建节点并返回指针
            2. var声明的指针默认为nil,无需显式初始化
            3. range遍历切片,返回索引和值(这里忽略索引)
            4. 方法接收者(l *LinkedList)使用指针,可修改原对象
    b.插入节点
        a.头部插入
            在链表头部插入新节点,时间复杂度O(1)。
        b.尾部插入
            在链表尾部插入新节点,需要遍历到尾部,时间复杂度O(n)。
        c.指定位置插入
            在指定位置插入新节点,需要找到前驱节点。
        d.代码示例
            ---
            // Java实现
            // 头部插入 - 时间复杂度O(1)
            public void addFirst(int val) {
                ListNode node = new ListNode(val);  // 创建新节点
                node.next = head;  // 新节点指向原头节点
                head = node;       // 更新头节点
                size++;            // 长度加1
            }
            // 📊 执行示例:
            // 原链表:1 -> 2 -> 3 -> null
            // 插入5:5 -> 1 -> 2 -> 3 -> null

            // 尾部插入 - 时间复杂度O(n)
            public void addLast(int val) {
                ListNode node = new ListNode(val);  // 创建新节点
                if (head == null) {
                    head = node;  // 空链表,直接设为头节点
                } else {
                    ListNode curr = head;  // 从头开始遍历
                    while (curr.next != null) {  // 找到最后一个节点
                        curr = curr.next;
                    }
                    curr.next = node;  // 尾节点指向新节点
                }
                size++;  // 长度加1
            }
            // 📊 执行示例:
            // 原链表:1 -> 2 -> 3 -> null
            // 插入5:1 -> 2 -> 3 -> 5 -> null
            // 遍历过程:curr从1移动到2,再到3,最后在3后插入5

            // 指定位置插入 - 时间复杂度O(n)
            public void add(int index, int val) {
                // 边界检查:索引必须在[0, size]范围内
                if (index < 0 || index > size) {
                    throw new IndexOutOfBoundsException();
                }
                // 特殊情况:头部插入
                if (index == 0) {
                    addFirst(val);
                    return;
                }
                ListNode node = new ListNode(val);  // 创建新节点
                ListNode prev = head;  // prev指向插入位置的前一个节点
                // 找到第index-1个节点(前驱节点)
                for (int i = 0; i < index - 1; i++) {
                    prev = prev.next;
                }
                node.next = prev.next;  // 新节点指向后继节点
                prev.next = node;       // 前驱节点指向新节点
                size++;  // 长度加1
            }
            // 📊 执行示例:
            // 原链表:1 -> 2 -> 3 -> null
            // 在索引2插入5:1 -> 2 -> 5 -> 3 -> null
            // 步骤:
            //   1. prev移动到索引1(节点2)
            //   2. node.next = prev.next (节点3)
            //   3. prev.next = node (节点2指向节点5)
            ---

        e.图解说明
            📌 指定位置插入可视化(在索引2插入5):

            原链表:
            head
              ↓
            [1] -> [2] -> [3] -> null
            索引: 0     1     2

            步骤1:找到前驱节点(index-1=1)
            head      prev
              ↓        ↓
            [1] -> [2] -> [3] -> null

            步骤2:创建新节点,设置next
                   prev   [5]
                     ↓     ↓
            [1] -> [2]    [3] -> null
                     ↓     ↑
                     +-----+

            步骤3:更新prev.next
            head      prev
              ↓        ↓
            [1] -> [2] -> [5] -> [3] -> null

        f.常见错误与陷阱
            ⚠️ 指定位置插入常见错误:
            1. 指针顺序错误
               错误:prev.next = node; node.next = prev.next;
               后果:node指向自己,形成死循环
               正确:必须先node.next = prev.next,再prev.next = node

            2. 边界检查遗漏
               错误:允许index > size
               后果:可能导致空指针异常
               正确:index应在[0, size]范围内

            3. 头部插入特殊处理
               错误:index=0时也执行通用逻辑
               后果:prev为null,空指针异常
               正确:index=0时调用addFirst()

            4. 尾部插入效率低
               问题:每次都需要O(n)遍历
               优化:维护tail指针,尾部插入变为O(1)
    c.删除节点
        a.功能说明
            删除指定位置或指定值的节点,需要找到前驱节点。
        b.代码示例
            ---
            // Java实现
            // 删除头节点 - 时间复杂度O(1)
            public int removeFirst() {
                if (head == null) {  // 边界检查:空链表
                    throw new NoSuchElementException();
                }
                int val = head.val;  // 保存要删除的值
                head = head.next;    // 头节点后移
                size--;              // 长度减1
                return val;          // 返回删除的值
            }
            // 📊 执行示例:
            // 原链表:1 -> 2 -> 3 -> null
            // 删除头:2 -> 3 -> null,返回1

            // 删除指定位置节点 - 时间复杂度O(n)
            public int remove(int index) {
                // 边界检查:索引必须在[0, size)范围内
                if (index < 0 || index >= size) {
                    throw new IndexOutOfBoundsException();
                }
                // 特殊情况:删除头节点
                if (index == 0) {
                    return removeFirst();
                }
                ListNode prev = head;  // prev指向要删除节点的前一个节点
                // 找到第index-1个节点
                for (int i = 0; i < index - 1; i++) {
                    prev = prev.next;
                }
                int val = prev.next.val;      // 保存要删除的值
                prev.next = prev.next.next;   // 跳过要删除的节点
                size--;                       // 长度减1
                return val;
            }
            // 📊 执行示例:
            // 原链表:1 -> 2 -> 3 -> null
            // 删除索引1:1 -> 3 -> null,返回2
            // 步骤:prev移动到节点1,然后prev.next指向节点3

            // 删除指定值的节点 - 时间复杂度O(n)
            public boolean removeValue(int val) {
                if (head == null) return false;  // 空链表
                // 特殊情况:头节点就是目标值
                if (head.val == val) {
                    head = head.next;
                    size--;
                    return true;
                }
                ListNode prev = head;  // prev指向当前节点的前一个节点
                // 查找目标值,停止条件:到达尾部或找到目标
                while (prev.next != null && prev.next.val != val) {
                    prev = prev.next;
                }
                if (prev.next == null) return false;  // 未找到
                prev.next = prev.next.next;  // 跳过目标节点
                size--;
                return true;
            }
            // 📊 执行示例:
            // 原链表:1 -> 2 -> 3 -> null
            // 删除值2:1 -> 3 -> null,返回true
            // 删除值5:1 -> 2 -> 3 -> null,返回false(未找到)
            ---

        c.Go实现
            ---
            // RemoveFirst 删除头节点
            func (l *LinkedList) RemoveFirst() int {
                if l.head == nil {
                    panic("空链表")
                }
                val := l.head.Val
                l.head = l.head.Next
                l.size--
                return val
            }

            // Remove 删除指定位置节点
            func (l *LinkedList) Remove(index int) int {
                if index < 0 || index >= l.size {
                    panic("index out of bounds")
                }
                if index == 0 {
                    return l.RemoveFirst()
                }
                prev := l.head
                for i := 0; i < index-1; i++ {
                    prev = prev.Next
                }
                val := prev.Next.Val
                prev.Next = prev.Next.Next
                l.size--
                return val
            }

            // RemoveValue 删除指定值的节点
            func (l *LinkedList) RemoveValue(val int) bool {
                if l.head == nil {
                    return false
                }
                if l.head.Val == val {
                    l.head = l.head.Next
                    l.size--
                    return true
                }
                prev := l.head
                for prev.Next != nil && prev.Next.Val != val {
                    prev = prev.Next
                }
                if prev.Next == nil {
                    return false
                }
                prev.Next = prev.Next.Next
                l.size--
                return true
            }
            ---

        d.图解说明
            📌 删除指定位置节点可视化(删除索引1):

            原链表:
            head
              ↓
            [1] -> [2] -> [3] -> null
            索引: 0     1     2

            步骤1:找到前驱节点(index-1=0)
            head/prev
              ↓
            [1] -> [2] -> [3] -> null
                   ↑
                 要删除

            步骤2:跳过要删除的节点
            head/prev
              ↓
            [1] -----> [3] -> null
              ↓
            [2] (已断开连接)

            最终结果:
            head
              ↓
            [1] -> [3] -> null

        e.常见错误与陷阱
            ⚠️ 删除操作常见错误:
            1. 空指针异常
               错误:未检查prev.next是否为null
               后果:prev.next.val或prev.next.next导致空指针异常
               正确:删除前必须检查节点是否存在

            2. 内存泄漏(Java不存在,C/C++需注意)
               问题:删除节点后未释放内存
               Java:自动垃圾回收,无需手动处理
               Go:自动垃圾回收,无需手动处理

            3. size未更新
               错误:删除节点后忘记size--
               后果:链表长度统计不准确

            4. 删除值时只删除第一个
               问题:如果有多个相同值,只删除第一个
               解决:如需删除所有,使用循环持续查找

02.单链表查找
    a.按索引查找
        a.功能说明
            根据索引查找节点,需要从头遍历,时间复杂度O(n)。
        b.代码示例
            ---
            // Java实现
            public int get(int index) {
                if (index < 0 || index >= size) {
                    throw new IndexOutOfBoundsException();
                }
                ListNode curr = head;
                for (int i = 0; i < index; i++) {
                    curr = curr.next;
                }
                return curr.val;
            }

            // Go实现
            func (l *LinkedList) Get(index int) int {
                if index < 0 || index >= l.size {
                    panic("index out of bounds")
                }
                curr := l.head
                for i := 0; i < index; i++ {
                    curr = curr.Next
                }
                return curr.Val
            }
            ---
    b.按值查找
        a.功能说明
            查找第一个值等于target的节点,返回索引。
        b.代码示例
            ---
            // Java实现
            public int indexOf(int val) {
                ListNode curr = head;
                int index = 0;
                while (curr != null) {
                    if (curr.val == val) {
                        return index;
                    }
                    curr = curr.next;
                    index++;
                }
                return -1;
            }
            ---

03.单链表遍历
    a.迭代遍历
        a.功能说明
            使用循环遍历链表的所有节点。
        b.代码示例
            ---
            // Java实现
            public void traverse() {
                ListNode curr = head;
                while (curr != null) {
                    System.out.print(curr.val + " -> ");
                    curr = curr.next;
                }
                System.out.println("null");
            }

            // Go实现
            func (l *LinkedList) Traverse() {
                curr := l.head
                for curr != nil {
                    fmt.Printf("%d -> ", curr.Val)
                    curr = curr.Next
                }
                fmt.Println("nil")
            }
            ---
    b.递归遍历
        a.功能说明
            使用递归方式遍历链表。
        b.代码示例
            ---
            // Java实现
            public void traverseRecursive(ListNode node) {
                if (node == null) {
                    System.out.println("null");
                    return;
                }
                System.out.print(node.val + " -> ");
                traverseRecursive(node.next);
            }
            ---

2.5 双向链表实现

01.双向链表概述
    a.定义
        双向链表的每个节点包含两个指针,分别指向前驱节点和后继节点。
    b.优点
        可以双向遍历,删除节点时不需要找前驱节点。
    c.缺点
        需要额外的指针空间,维护两个指针增加了复杂度。

02.节点定义
    a.Java实现
        a.功能说明
            定义双向链表节点,包含数据域、前驱指针和后继指针。
        b.代码示例
            ---
            // 双向链表节点
            class DoublyListNode {
                int val;
                DoublyListNode prev;
                DoublyListNode next;

                DoublyListNode(int val) {
                    this.val = val;
                }
            }

            // 双向链表类
            public class DoublyLinkedList {
                private DoublyListNode head;
                private DoublyListNode tail;
                private int size;

                public DoublyLinkedList() {
                    head = null;
                    tail = null;
                    size = 0;
                }
            }
            ---
    b.Go实现
        a.功能说明
            Go语言中定义双向链表节点结构体。
        b.代码示例
            ---
            // 双向链表节点
            type DoublyListNode struct {
                Val  int
                Prev *DoublyListNode
                Next *DoublyListNode
            }

            // 双向链表
            type DoublyLinkedList struct {
                head *DoublyListNode
                tail *DoublyListNode
                size int
            }

            func NewDoublyLinkedList() *DoublyLinkedList {
                return &DoublyLinkedList{}
            }
            ---

03.双向链表基本操作
    a.头部插入
        a.功能说明
            在链表头部插入新节点,需要更新head和新节点的prev、next指针。
        b.代码示例
            ---
            // Java实现
            public void addFirst(int val) {
                DoublyListNode node = new DoublyListNode(val);
                if (head == null) {
                    head = tail = node;
                } else {
                    node.next = head;
                    head.prev = node;
                    head = node;
                }
                size++;
            }

            // Go实现
            func (l *DoublyLinkedList) AddFirst(val int) {
                node := &DoublyListNode{Val: val}
                if l.head == nil {
                    l.head = node
                    l.tail = node
                } else {
                    node.Next = l.head
                    l.head.Prev = node
                    l.head = node
                }
                l.size++
            }
            ---
    b.尾部插入
        a.功能说明
            在链表尾部插入新节点,通过tail指针可以O(1)时间完成。
        b.代码示例
            ---
            // Java实现
            public void addLast(int val) {
                DoublyListNode node = new DoublyListNode(val);
                if (tail == null) {
                    head = tail = node;
                } else {
                    tail.next = node;
                    node.prev = tail;
                    tail = node;
                }
                size++;
            }
            ---
    c.删除节点
        a.功能说明
            删除指定节点,需要更新前驱和后继节点的指针。
        b.代码示例
            ---
            // Java实现
            public void remove(DoublyListNode node) {
                if (node == null) return;

                if (node.prev != null) {
                    node.prev.next = node.next;
                } else {
                    head = node.next;
                }

                if (node.next != null) {
                    node.next.prev = node.prev;
                } else {
                    tail = node.prev;
                }

                size--;
            }

            // 删除头节点
            public int removeFirst() {
                if (head == null) {
                    throw new NoSuchElementException();
                }
                int val = head.val;
                if (head == tail) {
                    head = tail = null;
                } else {
                    head = head.next;
                    head.prev = null;
                }
                size--;
                return val;
            }

            // 删除尾节点
            public int removeLast() {
                if (tail == null) {
                    throw new NoSuchElementException();
                }
                int val = tail.val;
                if (head == tail) {
                    head = tail = null;
                } else {
                    tail = tail.prev;
                    tail.next = null;
                }
                size--;
                return val;
            }
            ---

04.双向链表遍历
    a.正向遍历
        a.功能说明
            从头节点开始向后遍历。
        b.代码示例
            ---
            // Java实现
            public void traverseForward() {
                DoublyListNode curr = head;
                while (curr != null) {
                    System.out.print(curr.val + " <-> ");
                    curr = curr.next;
                }
                System.out.println("null");
            }
            ---
    b.反向遍历
        a.功能说明
            从尾节点开始向前遍历,这是双向链表的优势。
        b.代码示例
            ---
            // Java实现
            public void traverseBackward() {
                DoublyListNode curr = tail;
                while (curr != null) {
                    System.out.print(curr.val + " <-> ");
                    curr = curr.prev;
                }
                System.out.println("null");
            }

            // Go实现
            func (l *DoublyLinkedList) TraverseBackward() {
                curr := l.tail
                for curr != nil {
                    fmt.Printf("%d <-> ", curr.Val)
                    curr = curr.Prev
                }
                fmt.Println("nil")
            }
            ---

05.Java LinkedList源码分析
    a.底层实现
        Java的LinkedList是基于双向链表实现的,同时实现了List和Deque接口。
    b.核心特点
        a.双向链表
            每个节点包含prev和next指针。
        b.支持队列操作
            可以作为栈、队列、双端队列使用。
        c.非线程安全
            多线程环境需要外部同步。
    c.常用方法
        a.代码示例
            ---
            import java.util.LinkedList;

            LinkedList<Integer> list = new LinkedList<>();

            // 添加元素
            list.add(10);               // 尾部添加
            list.addFirst(5);           // 头部添加
            list.addLast(15);           // 尾部添加

            // 获取元素
            int first = list.getFirst();    // 获取头部元素
            int last = list.getLast();      // 获取尾部元素

            // 删除元素
            list.removeFirst();         // 删除头部
            list.removeLast();          // 删除尾部

            // 作为栈使用
            list.push(20);              // 入栈
            int top = list.pop();       // 出栈

            // 作为队列使用
            list.offer(30);             // 入队
            int head = list.poll();     // 出队
            ---

2.6 循环链表

01.循环链表概述
    a.定义
        循环链表是一种特殊的链表,最后一个节点的指针指向头节点,形成一个环。
    b.分类
        a.单向循环链表
            尾节点的next指针指向头节点。
        b.双向循环链表
            尾节点的next指向头节点,头节点的prev指向尾节点。
    c.特点
        可以从任意节点出发遍历整个链表,适合循环处理的场景。

02.单向循环链表实现
    a.Java实现
        ---
        public class CircularLinkedList {
            private ListNode head;
            private int size;

            // 尾部插入
            public void add(int val) {
                ListNode node = new ListNode(val);
                if (head == null) {
                    head = node;
                    node.next = head;
                } else {
                    ListNode curr = head;
                    while (curr.next != head) {
                        curr = curr.next;
                    }
                    curr.next = node;
                    node.next = head;
                }
                size++;
            }

            // 遍历
            public void traverse() {
                if (head == null) return;
                ListNode curr = head;
                do {
                    System.out.print(curr.val + " -> ");
                    curr = curr.next;
                } while (curr != head);
                System.out.println("(循环)");
            }
        }
        ---
    b.Go实现
        ---
        type CircularLinkedList struct {
            head *ListNode
            size int
        }

        func (l *CircularLinkedList) Add(val int) {
            node := &ListNode{Val: val}
            if l.head == nil {
                l.head = node
                node.Next = l.head
            } else {
                curr := l.head
                for curr.Next != l.head {
                    curr = curr.Next
                }
                curr.Next = node
                node.Next = l.head
            }
            l.size++
        }
        ---

03.应用场景
    a.约瑟夫问题
        N个人围成一圈,从第1个人开始报数,报到M的人出列,求最后剩下的人。
    b.循环调度
        操作系统的时间片轮转调度算法。
    c.循环缓冲区
        音视频播放器的循环缓冲区。

2.7 经典题目

01.LeetCode链表题目
    a.反转链表
        a.题目
            LeetCode 206 - 反转单链表。
        b.Java解法
            ---
            public ListNode reverseList(ListNode head) {
                ListNode prev = null;
                ListNode curr = head;
                while (curr != null) {
                    ListNode next = curr.next;
                    curr.next = prev;
                    prev = curr;
                    curr = next;
                }
                return prev;
            }
            ---
        c.Go解法
            ---
            func reverseList(head *ListNode) *ListNode {
                var prev *ListNode
                curr := head
                for curr != nil {
                    next := curr.Next
                    curr.Next = prev
                    prev = curr
                    curr = next
                }
                return prev
            }
            ---
    b.合并两个有序链表
        a.题目
            LeetCode 21 - 合并两个升序链表。
        b.Java解法
            ---
            public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
                ListNode dummy = new ListNode(0);
                ListNode curr = dummy;
                while (l1 != null && l2 != null) {
                    if (l1.val < l2.val) {
                        curr.next = l1;
                        l1 = l1.next;
                    } else {
                        curr.next = l2;
                        l2 = l2.next;
                    }
                    curr = curr.next;
                }
                curr.next = (l1 != null) ? l1 : l2;
                return dummy.next;
            }
            ---
    c.环形链表
        a.题目
            LeetCode 141 - 判断链表是否有环。
        b.快慢指针解法
            ---
            public boolean hasCycle(ListNode head) {
                if (head == null) return false;
                ListNode slow = head;
                ListNode fast = head;
                while (fast != null && fast.next != null) {
                    slow = slow.next;
                    fast = fast.next.next;
                    if (slow == fast) return true;
                }
                return false;
            }
            ---

02.常见链表操作
    a.删除倒数第N个节点
        LeetCode 19 - 使用快慢指针,快指针先走N步。
    b.链表的中间节点
        LeetCode 876 - 使用快慢指针,快指针走两步慢指针走一步。
    c.回文链表
        LeetCode 234 - 找到中点,反转后半部分,比较前后两部分。
    d.相交链表
        LeetCode 160 - 双指针法,消除长度差。

3 栈与队列

3.1 栈的概念与实现

01.栈的定义
    a.基本概念
        栈是一种后进先出LIFO的线性数据结构,只能在栈顶进行插入和删除操作。
    b.核心操作
        a.push入栈
            在栈顶添加元素,时间复杂度O(1)。
        b.pop出栈
            删除并返回栈顶元素,时间复杂度O(1)。
        c.peek查看栈顶
            返回栈顶元素但不删除,时间复杂度O(1)。
        d.isEmpty判空
            判断栈是否为空。
    c.栈的特点
        后进先出、只能访问栈顶元素、操作受限的线性表。

02.数组实现栈
    a.Java实现
        ---
        public class ArrayStack {
            private int[] data;
            private int top;
            private int capacity;

            public ArrayStack(int capacity) {
                this.capacity = capacity;
                data = new int[capacity];
                top = -1;
            }

            public void push(int val) {
                if (top == capacity - 1) {
                    throw new RuntimeException("栈已满");
                }
                data[++top] = val;
            }

            public int pop() {
                if (isEmpty()) {
                    throw new RuntimeException("栈为空");
                }
                return data[top--];
            }

            public int peek() {
                if (isEmpty()) {
                    throw new RuntimeException("栈为空");
                }
                return data[top];
            }

            public boolean isEmpty() {
                return top == -1;
            }

            public int size() {
                return top + 1;
            }
        }
        ---
    b.Go实现
        ---
        type ArrayStack struct {
            data []int
            top  int
        }

        func NewArrayStack(capacity int) *ArrayStack {
            return &ArrayStack{
                data: make([]int, capacity),
                top:  -1,
            }
        }

        func (s *ArrayStack) Push(val int) {
            if s.top == len(s.data)-1 {
                panic("栈已满")
            }
            s.top++
            s.data[s.top] = val
        }

        func (s *ArrayStack) Pop() int {
            if s.IsEmpty() {
                panic("栈为空")
            }
            val := s.data[s.top]
            s.top--
            return val
        }

        func (s *ArrayStack) Peek() int {
            if s.IsEmpty() {
                panic("栈为空")
            }
            return s.data[s.top]
        }

        func (s *ArrayStack) IsEmpty() bool {
            return s.top == -1
        }
        ---

03.链表实现栈
    a.Java实现
        ---
        public class LinkedStack {
            private ListNode top;
            private int size;

            public void push(int val) {
                ListNode node = new ListNode(val);
                node.next = top;
                top = node;
                size++;
            }

            public int pop() {
                if (isEmpty()) {
                    throw new RuntimeException("栈为空");
                }
                int val = top.val;
                top = top.next;
                size--;
                return val;
            }

            public int peek() {
                if (isEmpty()) {
                    throw new RuntimeException("栈为空");
                }
                return top.val;
            }

            public boolean isEmpty() {
                return top == null;
            }
        }
        ---

04.Java Stack类
    a.使用示例
        ---
        import java.util.Stack;

        Stack<Integer> stack = new Stack<>();
        stack.push(10);
        stack.push(20);
        int top = stack.peek();    // 20
        int val = stack.pop();     // 20
        boolean empty = stack.isEmpty();
        ---
    b.注意事项
        Stack继承自Vector,是线程安全的但性能较差,推荐使用Deque接口的实现类。

3.2 栈的应用场景

01.表达式求值
    a.中缀表达式转后缀
        使用栈将中缀表达式(如3+4*5)转换为后缀表达式(如345*+)。
    b.后缀表达式求值
        a.算法步骤
            遇到数字入栈,遇到运算符弹出两个数字计算后入栈。
        b.Java实现
            ---
            public int evalRPN(String[] tokens) {
                Stack<Integer> stack = new Stack<>();
                for (String token : tokens) {
                    if (token.equals("+") || token.equals("-") ||
                        token.equals("*") || token.equals("/")) {
                        int b = stack.pop();
                        int a = stack.pop();
                        int result = 0;
                        switch (token) {
                            case "+": result = a + b; break;
                            case "-": result = a - b; break;
                            case "*": result = a * b; break;
                            case "/": result = a / b; break;
                        }
                        stack.push(result);
                    } else {
                        stack.push(Integer.parseInt(token));
                    }
                }
                return stack.pop();
            }
            ---

02.括号匹配
    a.问题描述
        判断字符串中的括号是否匹配,如"({[]})"是匹配的,"({[})"不匹配。
    b.解决方案
        a.算法思路
            遇到左括号入栈,遇到右括号弹出栈顶检查是否匹配。
        b.Java实现
            ---
            public boolean isValid(String s) {
                Stack<Character> stack = new Stack<>();
                for (char c : s.toCharArray()) {
                    if (c == '(' || c == '[' || c == '{') {
                        stack.push(c);
                    } else {
                        if (stack.isEmpty()) return false;
                        char top = stack.pop();
                        if (c == ')' && top != '(') return false;
                        if (c == ']' && top != '[') return false;
                        if (c == '}' && top != '{') return false;
                    }
                }
                return stack.isEmpty();
            }
            ---
        c.Go实现
            ---
            func isValid(s string) bool {
                stack := []rune{}
                pairs := map[rune]rune{')': '(', ']': '[', '}': '{'}

                for _, c := range s {
                    if c == '(' || c == '[' || c == '{' {
                        stack = append(stack, c)
                    } else {
                        if len(stack) == 0 {
                            return false
                        }
                        if stack[len(stack)-1] != pairs[c] {
                            return false
                        }
                        stack = stack[:len(stack)-1]
                    }
                }
                return len(stack) == 0
            }
            ---

03.函数调用栈
    a.调用栈原理
        每次函数调用时,将返回地址和局部变量压入栈,函数返回时弹出。
    b.递归实现
        递归函数利用系统调用栈保存中间状态。
    c.栈溢出
        递归层数过深会导致栈溢出StackOverflowError。

04.浏览器前进后退
    a.实现原理
        使用两个栈,一个存储历史记录,一个存储前进记录。
    b.代码示例
        ---
        public class BrowserHistory {
            private Stack<String> backStack;
            private Stack<String> forwardStack;
            private String current;

            public BrowserHistory(String homepage) {
                backStack = new Stack<>();
                forwardStack = new Stack<>();
                current = homepage;
            }

            public void visit(String url) {
                backStack.push(current);
                current = url;
                forwardStack.clear();
            }

            public String back(int steps) {
                while (steps > 0 && !backStack.isEmpty()) {
                    forwardStack.push(current);
                    current = backStack.pop();
                    steps--;
                }
                return current;
            }

            public String forward(int steps) {
                while (steps > 0 && !forwardStack.isEmpty()) {
                    backStack.push(current);
                    current = forwardStack.pop();
                    steps--;
                }
                return current;
            }
        }
        ---

3.3 队列的概念与实现

01.队列的定义
    a.基本概念
        队列是一种先进先出FIFO的线性数据结构,在队尾插入元素,在队头删除元素。
    b.核心操作
        a.enqueue入队
            在队尾添加元素,时间复杂度O(1)。
        b.dequeue出队
            删除并返回队头元素,时间复杂度O(1)。
        c.peek查看队头
            返回队头元素但不删除。
        d.isEmpty判空
            判断队列是否为空。
    c.队列特点
        先进先出、只能访问队头和队尾、操作受限的线性表。

02.数组实现队列
    a.简单实现
        a.问题
            出队后队头位置浪费,需要移动元素或使用循环队列。
        b.Java实现
            ---
            public class ArrayQueue {
                private int[] data;
                private int front;
                private int rear;
                private int size;

                public ArrayQueue(int capacity) {
                    data = new int[capacity];
                    front = 0;
                    rear = 0;
                    size = 0;
                }

                public void enqueue(int val) {
                    if (size == data.length) {
                        throw new RuntimeException("队列已满");
                    }
                    data[rear] = val;
                    rear = (rear + 1) % data.length;
                    size++;
                }

                public int dequeue() {
                    if (isEmpty()) {
                        throw new RuntimeException("队列为空");
                    }
                    int val = data[front];
                    front = (front + 1) % data.length;
                    size--;
                    return val;
                }

                public int peek() {
                    if (isEmpty()) {
                        throw new RuntimeException("队列为空");
                    }
                    return data[front];
                }

                public boolean isEmpty() {
                    return size == 0;
                }
            }
            ---

03.链表实现队列
    a.Java实现
        ---
        public class LinkedQueue {
            private ListNode front;
            private ListNode rear;
            private int size;

            public void enqueue(int val) {
                ListNode node = new ListNode(val);
                if (rear == null) {
                    front = rear = node;
                } else {
                    rear.next = node;
                    rear = node;
                }
                size++;
            }

            public int dequeue() {
                if (isEmpty()) {
                    throw new RuntimeException("队列为空");
                }
                int val = front.val;
                front = front.next;
                if (front == null) {
                    rear = null;
                }
                size--;
                return val;
            }

            public int peek() {
                if (isEmpty()) {
                    throw new RuntimeException("队列为空");
                }
                return front.val;
            }

            public boolean isEmpty() {
                return front == null;
            }
        }
        ---
    b.Go实现
        ---
        type LinkedQueue struct {
            front *ListNode
            rear  *ListNode
            size  int
        }

        func (q *LinkedQueue) Enqueue(val int) {
            node := &ListNode{Val: val}
            if q.rear == nil {
                q.front = node
                q.rear = node
            } else {
                q.rear.Next = node
                q.rear = node
            }
            q.size++
        }

        func (q *LinkedQueue) Dequeue() int {
            if q.IsEmpty() {
                panic("队列为空")
            }
            val := q.front.Val
            q.front = q.front.Next
            if q.front == nil {
                q.rear = nil
            }
            q.size--
            return val
        }
        ---

04.Java Queue接口
    a.常用实现类
        a.LinkedList
            基于链表实现,支持队列和双端队列操作。
        b.ArrayDeque
            基于循环数组实现,性能优于LinkedList。
        c.PriorityQueue
            基于堆实现的优先队列。
    b.使用示例
        ---
        import java.util.Queue;
        import java.util.LinkedList;

        Queue<Integer> queue = new LinkedList<>();
        queue.offer(10);        // 入队
        queue.offer(20);
        int head = queue.peek(); // 查看队头
        int val = queue.poll();  // 出队
        boolean empty = queue.isEmpty();
        ---

3.4 循环队列

01.循环队列概述
    a.定义
        使用固定大小的数组实现队列,通过取模运算实现循环使用数组空间。
    b.优点
        避免了普通数组队列的空间浪费问题,充分利用数组空间。
    c.关键问题
        如何区分队列为空和队列已满的情况。

02.循环队列实现
    a.方案一:牺牲一个空间
        a.判断条件
            队空:front == rear,队满:(rear + 1) % capacity == front。
        b.Java实现
            ---
            public class CircularQueue {
                private int[] data;
                private int front;
                private int rear;
                private int capacity;

                public CircularQueue(int k) {
                    capacity = k + 1;  // 多分配一个空间
                    data = new int[capacity];
                    front = 0;
                    rear = 0;
                }

                public boolean enQueue(int value) {
                    if (isFull()) return false;
                    data[rear] = value;
                    rear = (rear + 1) % capacity;
                    return true;
                }

                public boolean deQueue() {
                    if (isEmpty()) return false;
                    front = (front + 1) % capacity;
                    return true;
                }

                public int Front() {
                    if (isEmpty()) return -1;
                    return data[front];
                }

                public int Rear() {
                    if (isEmpty()) return -1;
                    return data[(rear - 1 + capacity) % capacity];
                }

                public boolean isEmpty() {
                    return front == rear;
                }

                public boolean isFull() {
                    return (rear + 1) % capacity == front;
                }
            }
            ---
    b.方案二:使用size变量
        a.判断条件
            队空:size == 0,队满:size == capacity。
        b.Go实现
            ---
            type CircularQueue struct {
                data     []int
                front    int
                rear     int
                size     int
                capacity int
            }

            func NewCircularQueue(k int) *CircularQueue {
                return &CircularQueue{
                    data:     make([]int, k),
                    capacity: k,
                }
            }

            func (q *CircularQueue) EnQueue(value int) bool {
                if q.IsFull() {
                    return false
                }
                q.data[q.rear] = value
                q.rear = (q.rear + 1) % q.capacity
                q.size++
                return true
            }

            func (q *CircularQueue) DeQueue() bool {
                if q.IsEmpty() {
                    return false
                }
                q.front = (q.front + 1) % q.capacity
                q.size--
                return true
            }

            func (q *CircularQueue) IsEmpty() bool {
                return q.size == 0
            }

            func (q *CircularQueue) IsFull() bool {
                return q.size == q.capacity
            }
            ---

03.LeetCode题目
    a.设计循环队列
        LeetCode 622 - 实现循环队列的基本操作。
    b.设计循环双端队列
        LeetCode 641 - 支持在队头和队尾进行插入删除操作。

3.5 双端队列

01.双端队列概述
    a.定义
        双端队列Deque是一种可以在两端进行插入和删除操作的线性数据结构。
    b.核心操作
        addFirst、addLast、removeFirst、removeLast、peekFirst、peekLast。
    c.应用场景
        可以同时作为栈和队列使用,是更通用的数据结构。

02.Java Deque接口
    a.常用实现类
        a.ArrayDeque
            基于循环数组实现,性能优秀,推荐使用。
        b.LinkedList
            基于双向链表实现,也实现了Deque接口。
    b.使用示例
        ---
        import java.util.Deque;
        import java.util.ArrayDeque;

        Deque<Integer> deque = new ArrayDeque<>();

        // 作为栈使用
        deque.push(10);         // 入栈
        deque.push(20);
        int top = deque.pop();  // 出栈

        // 作为队列使用
        deque.offer(30);        // 入队
        deque.offer(40);
        int head = deque.poll(); // 出队

        // 双端操作
        deque.addFirst(5);      // 队头插入
        deque.addLast(50);      // 队尾插入
        deque.removeFirst();    // 队头删除
        deque.removeLast();     // 队尾删除
        ---

03.ArrayDeque实现原理
    a.底层结构
        使用循环数组实现,维护head和tail两个指针。
    b.扩容机制
        当容量不足时,扩容为原来的2倍。
    c.性能特点
        所有操作时间复杂度O(1),比LinkedList性能更好。

04.Go实现双端队列
    a.代码示例
        ---
        type Deque struct {
            data []int
        }

        func NewDeque() *Deque {
            return &Deque{data: []int{}}
        }

        func (d *Deque) AddFirst(val int) {
            d.data = append([]int{val}, d.data...)
        }

        func (d *Deque) AddLast(val int) {
            d.data = append(d.data, val)
        }

        func (d *Deque) RemoveFirst() int {
            if len(d.data) == 0 {
                panic("deque is empty")
            }
            val := d.data[0]
            d.data = d.data[1:]
            return val
        }

        func (d *Deque) RemoveLast() int {
            if len(d.data) == 0 {
                panic("deque is empty")
            }
            val := d.data[len(d.data)-1]
            d.data = d.data[:len(d.data)-1]
            return val
        }
        ---

3.6 优先队列

01.优先队列概述
    a.定义
        优先队列是一种特殊的队列,每次出队的是优先级最高(或最低)的元素。
    b.实现方式
        通常使用堆heap实现,保证插入和删除操作的时间复杂度为O(log n)。
    c.应用场景
        任务调度、Dijkstra算法、Huffman编码、Top K问题等。

02.Java PriorityQueue
    a.基本使用
        ---
        import java.util.PriorityQueue;

        // 默认是最小堆
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
        minHeap.offer(5);
        minHeap.offer(2);
        minHeap.offer(8);
        int min = minHeap.poll();  // 2

        // 最大堆(使用比较器)
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);
        maxHeap.offer(5);
        maxHeap.offer(2);
        maxHeap.offer(8);
        int max = maxHeap.poll();  // 8

        // 自定义对象
        PriorityQueue<Task> taskQueue = new PriorityQueue<>(
            (t1, t2) -> t1.priority - t2.priority
        );
        ---
    b.底层实现
        基于数组实现的二叉堆,使用完全二叉树的性质。
    c.时间复杂度
        插入O(log n)、删除最小值O(log n)、查看最小值O(1)。

03.Go实现优先队列
    a.使用container/heap
        ---
        import "container/heap"

        type IntHeap []int

        func (h IntHeap) Len() int           { return len(h) }
        func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
        func (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

        func (h *IntHeap) Push(x interface{}) {
            *h = append(*h, x.(int))
        }

        func (h *IntHeap) Pop() interface{} {
            old := *h
            n := len(old)
            x := old[n-1]
            *h = old[0 : n-1]
            return x
        }

        // 使用示例
        func main() {
            h := &IntHeap{5, 2, 8, 1}
            heap.Init(h)
            heap.Push(h, 3)
            min := heap.Pop(h)  // 1
        }
        ---

04.经典题目
    a.数据流中的第K大元素
        LeetCode 703 - 使用最小堆维护K个最大元素。
    b.前K个高频元素
        LeetCode 347 - 使用哈希表统计频率,优先队列找Top K。
    c.合并K个有序链表
        LeetCode 23 - 使用优先队列维护K个链表的当前最小节点。

3.7 单调栈

01.单调栈概述
    a.定义
        单调栈是一种特殊的栈,栈内元素保持单调递增或单调递减的性质。
    b.应用场景
        解决下一个更大元素、柱状图最大矩形等问题。
    c.时间复杂度
        每个元素最多入栈出栈一次,总时间复杂度O(n)。

02.下一个更大元素
    a.问题描述
        给定数组,找出每个元素右侧第一个比它大的元素。
    b.Java实现
        ---
        public int[] nextGreaterElement(int[] nums) {
            int n = nums.length;
            int[] result = new int[n];
            Arrays.fill(result, -1);
            Stack<Integer> stack = new Stack<>();

            for (int i = 0; i < n; i++) {
                while (!stack.isEmpty() && nums[i] > nums[stack.peek()]) {
                    int index = stack.pop();
                    result[index] = nums[i];
                }
                stack.push(i);
            }
            return result;
        }
        ---
    c.Go实现
        ---
        func nextGreaterElement(nums []int) []int {
            n := len(nums)
            result := make([]int, n)
            for i := range result {
                result[i] = -1
            }
            stack := []int{}

            for i := 0; i < n; i++ {
                for len(stack) > 0 && nums[i] > nums[stack[len(stack)-1]] {
                    index := stack[len(stack)-1]
                    stack = stack[:len(stack)-1]
                    result[index] = nums[i]
                }
                stack = append(stack, i)
            }
            return result
        }
        ---

03.柱状图中最大的矩形
    a.问题描述
        LeetCode 84 - 给定柱状图的高度数组,求最大矩形面积。
    b.单调栈解法
        ---
        public int largestRectangleArea(int[] heights) {
            Stack<Integer> stack = new Stack<>();
            int maxArea = 0;
            int n = heights.length;

            for (int i = 0; i <= n; i++) {
                int h = (i == n) ? 0 : heights[i];
                while (!stack.isEmpty() && h < heights[stack.peek()]) {
                    int height = heights[stack.pop()];
                    int width = stack.isEmpty() ? i : i - stack.peek() - 1;
                    maxArea = Math.max(maxArea, height * width);
                }
                stack.push(i);
            }
            return maxArea;
        }
        ---

04.经典题目
    a.每日温度
        LeetCode 739 - 找出每天之后第一个更高温度的天数。
    b.接雨水
        LeetCode 42 - 使用单调栈计算能接多少雨水。

3.8 单调队列

01.单调队列概述
    a.定义
        单调队列是一种特殊的双端队列,队列内元素保持单调性。
    b.应用场景
        滑动窗口最大值、滑动窗口最小值等问题。
    c.核心思想
        维护一个单调递减(或递增)的队列,队头始终是当前窗口的最大(或最小)值。

02.滑动窗口最大值
    a.问题描述
        LeetCode 239 - 给定数组和窗口大小k,求每个窗口的最大值。
    b.Java实现
        ---
        public int[] maxSlidingWindow(int[] nums, int k) {
            if (nums == null || nums.length == 0) return new int[0];

            int n = nums.length;
            int[] result = new int[n - k + 1];
            Deque<Integer> deque = new ArrayDeque<>();

            for (int i = 0; i < n; i++) {
                // 移除窗口外的元素
                while (!deque.isEmpty() && deque.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;
        }
        ---
    c.Go实现
        ---
        func maxSlidingWindow(nums []int, k int) []int {
            if len(nums) == 0 {
                return []int{}
            }

            result := []int{}
            deque := []int{}

            for i := 0; i < len(nums); i++ {
                // 移除窗口外的元素
                for len(deque) > 0 && deque[0] < i-k+1 {
                    deque = deque[1:]
                }

                // 维护单调递减队列
                for len(deque) > 0 && nums[i] > nums[deque[len(deque)-1]] {
                    deque = deque[:len(deque)-1]
                }

                deque = append(deque, i)

                // 记录窗口最大值
                if i >= k-1 {
                    result = append(result, nums[deque[0]])
                }
            }
            return result
        }
        ---

03.算法分析
    a.时间复杂度
        每个元素最多入队出队一次,总时间复杂度O(n)。
    b.空间复杂度
        队列最多存储k个元素,空间复杂度O(k)。
    c.优势
        相比暴力法O(n*k),单调队列将时间复杂度降低到O(n)。

04.相关题目
    a.滑动窗口最小值
        维护单调递增队列。
    b.和至少为K的最短子数组
        LeetCode 862 - 使用单调队列优化前缀和。

4 树结构

4.1 树的基本概念

01.树的定义
    a.基本概念
        树是n个节点的有限集合,有且仅有一个特定的称为根的节点,其余节点可分为m个互不相交的有限集合。
    b.基本术语
        a.节点
            树中的数据元素。
        b.根节点
            树的顶端节点,没有父节点。
        c.叶子节点
            没有子节点的节点。
        d.节点的度
            节点拥有的子树数量。
        e.树的高度
            从根节点到叶子节点的最长路径长度。
    c.树的性质
        节点数等于边数加1,树中不存在环路。

02.二叉树定义
    a.基本概念
        每个节点最多有两个子节点的树结构,分为左子树和右子树。
    b.特殊二叉树
        a.满二叉树
            每层节点都达到最大数量。
        b.完全二叉树
            除最后一层外,其他层节点数达到最大,最后一层节点靠左排列。
        c.二叉搜索树
            左子树所有节点值小于根节点,右子树所有节点值大于根节点。
    c.二叉树性质
        第i层最多有2的i-1次方个节点,高度为h的二叉树最多有2的h次方减1个节点。

03.节点定义
    a.Java实现
        ---
        class TreeNode {
            int val;
            TreeNode left;
            TreeNode right;

            TreeNode(int val) {
                this.val = val;
            }
        }
        ---
    b.Go实现
        ---
        type TreeNode struct {
            Val   int
            Left  *TreeNode
            Right *TreeNode
        }
        ---

4.2 二叉树

01.二叉树的创建
    a.Java实现
        ---
        public class BinaryTree {
            private TreeNode root;

            // 根据数组创建二叉树(层序遍历)
            public TreeNode buildTree(Integer[] arr) {
                if (arr == null || arr.length == 0 || arr[0] == null) {
                    return null;
                }

                root = new TreeNode(arr[0]);
                Queue<TreeNode> queue = new LinkedList<>();
                queue.offer(root);
                int i = 1;

                while (!queue.isEmpty() && i < arr.length) {
                    TreeNode node = queue.poll();

                    if (i < arr.length && arr[i] != null) {
                        node.left = new TreeNode(arr[i]);
                        queue.offer(node.left);
                    }
                    i++;

                    if (i < arr.length && arr[i] != null) {
                        node.right = new TreeNode(arr[i]);
                        queue.offer(node.right);
                    }
                    i++;
                }
                return root;
            }
        }
        ---

02.二叉树的基本操作
    a.计算节点数
        ---
        public int countNodes(TreeNode root) {
            if (root == null) return 0;
            return 1 + countNodes(root.left) + countNodes(root.right);
        }
        ---
    b.计算树的高度
        ---
        public int maxDepth(TreeNode root) {
            if (root == null) return 0;
            return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
        }
        ---
    c.判断是否为平衡树
        ---
        public boolean isBalanced(TreeNode root) {
            return height(root) != -1;
        }

        private int height(TreeNode node) {
            if (node == null) return 0;
            int left = height(node.left);
            if (left == -1) return -1;
            int right = height(node.right);
            if (right == -1) return -1;
            if (Math.abs(left - right) > 1) return -1;
            return Math.max(left, right) + 1;
        }
        ---

4.3 二叉搜索树

01.BST定义
    a.基本性质
        左子树所有节点值小于根节点,右子树所有节点值大于根节点,左右子树也是二叉搜索树。
    b.时间复杂度
        平均情况下查找、插入、删除的时间复杂度为O(log n),最坏情况O(n)。

02.BST基本操作
    a.查找
        ---
        public TreeNode search(TreeNode root, int val) {
            if (root == null || root.val == val) {
                return root;
            }
            if (val < root.val) {
                return search(root.left, val);
            } else {
                return search(root.right, val);
            }
        }
        ---
    b.插入
        ---
        public TreeNode insert(TreeNode root, int val) {
            if (root == null) {
                return new TreeNode(val);
            }
            if (val < root.val) {
                root.left = insert(root.left, val);
            } else if (val > root.val) {
                root.right = insert(root.right, val);
            }
            return root;
        }
        ---
    c.删除
        ---
        public TreeNode delete(TreeNode root, int val) {
            if (root == null) return null;

            if (val < root.val) {
                root.left = delete(root.left, val);
            } else if (val > root.val) {
                root.right = delete(root.right, val);
            } else {
                if (root.left == null) return root.right;
                if (root.right == null) return root.left;

                TreeNode minNode = findMin(root.right);
                root.val = minNode.val;
                root.right = delete(root.right, minNode.val);
            }
            return root;
        }

        private TreeNode findMin(TreeNode node) {
            while (node.left != null) {
                node = node.left;
            }
            return node;
        }
        ---

03.BST验证
    a.验证二叉搜索树
        ---
        public boolean isValidBST(TreeNode root) {
            return validate(root, null, null);
        }

        private boolean validate(TreeNode node, Integer min, Integer max) {
            if (node == null) return true;
            if (min != null && node.val <= min) return false;
            if (max != null && node.val >= max) return false;
            return validate(node.left, min, node.val) &&
                   validate(node.right, node.val, max);
        }
        ---

4.4 平衡二叉树

01.AVL树概述
    a.定义
        AVL树是一种自平衡的二叉搜索树,任何节点的两个子树高度差不超过1。
    b.平衡因子
        节点的左子树高度减去右子树高度,取值为-1、0、1。
    c.旋转操作
        通过左旋、右旋、左右旋、右左旋四种操作维持平衡。

02.红黑树概述
    a.定义
        红黑树是一种自平衡的二叉搜索树,通过节点颜色和旋转操作维持平衡。
    b.性质
        a.节点颜色
            每个节点是红色或黑色。
        b.根节点
            根节点是黑色。
        c.叶子节点
            所有叶子节点(NIL)是黑色。
        d.红色节点
            红色节点的子节点必须是黑色。
        e.黑色高度
            从任一节点到其叶子节点的所有路径包含相同数量的黑色节点。
    c.时间复杂度
        查找、插入、删除的时间复杂度均为O(log n)。

03.Java TreeMap
    a.底层实现
        Java的TreeMap基于红黑树实现,保证键的有序性。
    b.使用示例
        ---
        import java.util.TreeMap;

        TreeMap<Integer, String> map = new TreeMap<>();
        map.put(3, "three");
        map.put(1, "one");
        map.put(2, "two");

        // 有序遍历
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        // 范围查询
        Map<Integer, String> subMap = map.subMap(1, 3);
        Integer firstKey = map.firstKey();
        Integer lastKey = map.lastKey();
        ---

4.5 堆结构

01.堆的定义
    a.基本概念
        堆是一种完全二叉树,分为最大堆和最小堆。
    b.最大堆
        每个节点的值大于等于其子节点的值,根节点是最大值。
    c.最小堆
        每个节点的值小于等于其子节点的值,根节点是最小值。

02.堆的数组表示
    a.索引关系
        a.父节点
            索引为i的节点,其父节点索引为(i-1)/2。
        b.左子节点
            索引为i的节点,其左子节点索引为2*i+1。
        c.右子节点
            索引为i的节点,其右子节点索引为2*i+2。

03.堆的基本操作
    a.插入元素
        ---
        public void insert(int val) {
            heap.add(val);
            siftUp(heap.size() - 1);
        }

        private void siftUp(int i) {
            while (i > 0) {
                int parent = (i - 1) / 2;
                if (heap.get(i) >= heap.get(parent)) break;
                swap(i, parent);
                i = parent;
            }
        }
        ---
    b.删除最小值
        ---
        public int removeMin() {
            if (heap.isEmpty()) {
                throw new RuntimeException("堆为空");
            }
            int min = heap.get(0);
            int last = heap.remove(heap.size() - 1);
            if (!heap.isEmpty()) {
                heap.set(0, last);
                siftDown(0);
            }
            return min;
        }

        private void siftDown(int i) {
            int n = heap.size();
            while (2 * i + 1 < n) {
                int left = 2 * i + 1;
                int right = 2 * i + 2;
                int minChild = left;
                if (right < n && heap.get(right) < heap.get(left)) {
                    minChild = right;
                }
                if (heap.get(i) <= heap.get(minChild)) break;
                swap(i, minChild);
                i = minChild;
            }
        }
        ---

04.堆排序
    a.算法步骤
        建堆O(n),然后依次取出最大值O(n log n)。
    b.Java实现
        ---
        public void heapSort(int[] arr) {
            int n = arr.length;

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

            // 排序
            for (int i = n - 1; i > 0; i--) {
                swap(arr, 0, i);
                heapify(arr, i, 0);
            }
        }

        private void heapify(int[] arr, int n, int i) {
            int largest = i;
            int left = 2 * i + 1;
            int right = 2 * i + 2;

            if (left < n && arr[left] > arr[largest]) {
                largest = left;
            }
            if (right < n && arr[right] > arr[largest]) {
                largest = right;
            }
            if (largest != i) {
                swap(arr, i, largest);
                heapify(arr, n, largest);
            }
        }
        ---

4.6 树的遍历

01.前序遍历
    a.递归实现
        ---
        public void preorder(TreeNode root) {
            if (root == null) return;
            System.out.print(root.val + " ");
            preorder(root.left);
            preorder(root.right);
        }
        ---
    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 void inorder(TreeNode root) {
            if (root == null) return;
            inorder(root.left);
            System.out.print(root.val + " ");
            inorder(root.right);
        }
        ---
    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 void postorder(TreeNode root) {
            if (root == null) return;
            postorder(root.left);
            postorder(root.right);
            System.out.print(root.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;
        }
        ---
    b.Go实现
        ---
        func levelOrder(root *TreeNode) [][]int {
            result := [][]int{}
            if root == nil {
                return result
            }

            queue := []*TreeNode{root}
            for len(queue) > 0 {
                size := len(queue)
                level := []int{}
                for i := 0; i < size; i++ {
                    node := queue[0]
                    queue = queue[1:]
                    level = append(level, node.Val)
                    if node.Left != nil {
                        queue = append(queue, node.Left)
                    }
                    if node.Right != nil {
                        queue = append(queue, node.Right)
                    }
                }
                result = append(result, level)
            }
            return result
        }
        ---

4.7 经典题目

01.二叉树的最大深度
    a.题目
        LeetCode 104 - 计算二叉树的最大深度。
    b.递归解法
        ---
        public int maxDepth(TreeNode root) {
            if (root == null) return 0;
            return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
        }
        ---

02.翻转二叉树
    a.题目
        LeetCode 226 - 翻转二叉树的左右子树。
    b.递归解法
        ---
        public TreeNode invertTree(TreeNode root) {
            if (root == null) return null;
            TreeNode temp = root.left;
            root.left = invertTree(root.right);
            root.right = invertTree(temp);
            return root;
        }
        ---

03.对称二叉树
    a.题目
        LeetCode 101 - 判断二叉树是否对称。
    b.递归解法
        ---
        public boolean isSymmetric(TreeNode root) {
            return isMirror(root, root);
        }

        private boolean isMirror(TreeNode t1, TreeNode t2) {
            if (t1 == null && t2 == null) return true;
            if (t1 == null || t2 == null) return false;
            return t1.val == t2.val &&
                   isMirror(t1.left, t2.right) &&
                   isMirror(t1.right, t2.left);
        }
        ---

04.路径总和
    a.题目
        LeetCode 112 - 判断是否存在根到叶子节点的路径,使得路径上所有节点值之和等于目标值。
    b.递归解法
        ---
        public boolean hasPathSum(TreeNode root, int targetSum) {
            if (root == null) return false;
            if (root.left == null && root.right == null) {
                return root.val == targetSum;
            }
            return hasPathSum(root.left, targetSum - root.val) ||
                   hasPathSum(root.right, targetSum - root.val);
        }
        ---

05.二叉树的最近公共祖先
    a.题目
        LeetCode 236 - 找到两个节点的最近公共祖先。
    b.递归解法
        ---
        public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
            if (root == null || root == p || root == q) return root;
            TreeNode left = lowestCommonAncestor(root.left, p, q);
            TreeNode right = lowestCommonAncestor(root.right, p, q);
            if (left != null && right != null) return root;
            return left != null ? left : right;
        }
        ---

5 哈希表

5.1 哈希表原理

01.哈希表定义
    a.基本概念
        哈希表是一种根据键key直接访问值value的数据结构,通过哈希函数将键映射到数组索引。
    b.核心组成
        a.哈希函数
            将键转换为数组索引的函数。
        b.数组
            存储键值对的底层数据结构。
        c.冲突解决
            处理不同键映射到相同索引的方法。
    c.时间复杂度
        平均情况下查找、插入、删除的时间复杂度为O(1)。

02.哈希表的优点
    a.快速查找
        通过键可以在O(1)时间内找到对应的值。
    b.快速插入删除
        插入和删除操作也是O(1)时间复杂度。
    c.灵活的键类型
        键可以是任意可哈希的类型。

03.哈希表的缺点
    a.空间开销
        需要额外的空间存储哈希表,负载因子过低会浪费空间。
    b.无序性
        哈希表不保证元素的顺序。
    c.哈希冲突
        不同的键可能映射到相同的索引,需要额外处理。

5.2 哈希函数设计

01.哈希函数要求
    a.确定性
        相同的键必须产生相同的哈希值。
    b.均匀分布
        不同的键应该尽可能均匀地分布在哈希表中。
    c.高效计算
        哈希函数的计算应该快速。

02.常见哈希函数
    a.除留余数法
        h(key) = key % m,其中m通常选择质数。
    b.乘法哈希
        h(key) = floor(m * (key * A % 1)),其中A是常数。
    c.字符串哈希
        ---
        public int hashCode(String s) {
            int hash = 0;
            for (int i = 0; i < s.length(); i++) {
                hash = hash * 31 + s.charAt(i);
            }
            return hash;
        }
        ---

03.Java hashCode方法
    a.Object类
        默认返回对象的内存地址。
    b.String类
        ---
        public int hashCode() {
            int h = hash;
            if (h == 0 && value.length > 0) {
                for (int i = 0; i < value.length; i++) {
                    h = 31 * h + value[i];
                }
                hash = h;
            }
            return h;
        }
        ---
    c.自定义类
        ---
        class Person {
            String name;
            int age;

            @Override
            public int hashCode() {
                return Objects.hash(name, age);
            }

            @Override
            public boolean equals(Object obj) {
                if (this == obj) return true;
                if (obj == null || getClass() != obj.getClass()) return false;
                Person person = (Person) obj;
                return age == person.age && Objects.equals(name, person.name);
            }
        }
        ---

5.3 冲突解决方法

01.链地址法
    a.基本思想
        将哈希到同一位置的元素用链表连接起来。
    b.Java实现
        ---
        class HashTable {
            class Entry {
                int key;
                int value;
                Entry next;

                Entry(int key, int value) {
                    this.key = key;
                    this.value = value;
                }
            }

            private Entry[] table;
            private int capacity;
            private int size;

            public HashTable(int capacity) {
                this.capacity = capacity;
                table = new Entry[capacity];
            }

            public void put(int key, int value) {
                int index = hash(key);
                Entry entry = table[index];

                while (entry != null) {
                    if (entry.key == key) {
                        entry.value = value;
                        return;
                    }
                    entry = entry.next;
                }

                Entry newEntry = new Entry(key, value);
                newEntry.next = table[index];
                table[index] = newEntry;
                size++;
            }

            public Integer get(int key) {
                int index = hash(key);
                Entry entry = table[index];

                while (entry != null) {
                    if (entry.key == key) {
                        return entry.value;
                    }
                    entry = entry.next;
                }
                return null;
            }

            private int hash(int key) {
                return Math.abs(key) % capacity;
            }
        }
        ---

02.开放寻址法
    a.线性探测
        发生冲突时,依次检查下一个位置,直到找到空位。
    b.二次探测
        发生冲突时,按照1、4、9、16...的步长探测。
    c.双重哈希
        使用第二个哈希函数计算探测步长。

03.再哈希法
    a.基本思想
        当哈希表负载因子过高时,创建一个更大的哈希表,将所有元素重新哈希。
    b.扩容时机
        Java HashMap在负载因子超过0.75时扩容为原来的2倍。

5.4 Java HashMap实现

01.HashMap底层结构
    a.JDK 1.7
        数组加链表,使用头插法。
    b.JDK 1.8+
        数组加链表加红黑树,链表长度超过8且数组长度大于64时转换为红黑树。
    c.初始容量
        默认初始容量16,负载因子0.75。

02.HashMap基本操作
    a.使用示例
        ---
        import java.util.HashMap;
        import java.util.Map;

        Map<String, Integer> map = new HashMap<>();

        // 添加元素
        map.put("apple", 1);
        map.put("banana", 2);
        map.put("orange", 3);

        // 获取元素
        int value = map.get("apple");  // 1
        int defaultValue = map.getOrDefault("grape", 0);  // 0

        // 判断是否存在
        boolean hasKey = map.containsKey("apple");
        boolean hasValue = map.containsValue(1);

        // 删除元素
        map.remove("banana");

        // 遍历
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        // Java 8 forEach
        map.forEach((k, v) -> System.out.println(k + ": " + v));
        ---

03.HashMap核心方法
    a.put方法流程
        a.计算哈希值
            调用key的hashCode方法,然后进行扰动处理。
        b.定位桶位置
            通过(n-1) & hash计算数组索引。
        c.插入元素
            如果桶为空直接插入,否则遍历链表或红黑树插入。
        d.扩容检查
            如果size超过阈值,进行扩容。
    b.扩容机制
        ---
        // 扩容为原来的2倍
        void resize() {
            Node<K,V>[] oldTab = table;
            int oldCap = (oldTab == null) ? 0 : oldTab.length;
            int newCap = oldCap << 1;  // 扩容为2倍
            Node<K,V>[] newTab = new Node[newCap];

            // 重新哈希所有元素
            for (int i = 0; i < oldCap; i++) {
                Node<K,V> e = oldTab[i];
                if (e != null) {
                    // 将元素重新分配到新数组
                }
            }
            table = newTab;
        }
        ---

04.HashMap线程安全问题
    a.并发问题
        多线程环境下可能导致死循环、数据丢失等问题。
    b.解决方案
        a.Collections.synchronizedMap
            使用同步包装器。
        b.ConcurrentHashMap
            使用分段锁或CAS实现线程安全。
        c.Hashtable
            所有方法都加synchronized,性能较差。

5.5 Go Map实现

01.Go Map基础
    a.声明和初始化
        ---
        // 声明
        var m1 map[string]int

        // 使用make初始化
        m2 := make(map[string]int)
        m3 := make(map[string]int, 10)  // 指定初始容量

        // 字面量初始化
        m4 := map[string]int{
            "apple":  1,
            "banana": 2,
            "orange": 3,
        }
        ---
    b.基本操作
        ---
        m := make(map[string]int)

        // 添加元素
        m["apple"] = 1
        m["banana"] = 2

        // 获取元素
        value := m["apple"]  // 1

        // 检查键是否存在
        value, ok := m["grape"]
        if ok {
            fmt.Println("存在:", value)
        } else {
            fmt.Println("不存在")
        }

        // 删除元素
        delete(m, "banana")

        // 遍历
        for key, value := range m {
            fmt.Printf("%s: %d\n", key, value)
        }

        // 获取长度
        length := len(m)
        ---

02.Go Map底层实现
    a.数据结构
        使用哈希表实现,包含桶数组和溢出桶。
    b.扩容机制
        负载因子超过6.5时进行扩容,采用渐进式扩容。
    c.并发安全
        Go的map不是并发安全的,需要使用sync.Map或加锁。

03.sync.Map
    a.使用场景
        适用于读多写少的场景。
    b.使用示例
        ---
        import "sync"

        var m sync.Map

        // 存储
        m.Store("key1", "value1")

        // 读取
        value, ok := m.Load("key1")
        if ok {
            fmt.Println(value)
        }

        // 删除
        m.Delete("key1")

        // 遍历
        m.Range(func(key, value interface{}) bool {
            fmt.Printf("%v: %v\n", key, value)
            return true  // 返回false停止遍历
        })
        ---

5.6 应用场景

01.计数统计
    a.字符频率统计
        ---
        public Map<Character, Integer> countChars(String s) {
            Map<Character, Integer> map = new HashMap<>();
            for (char c : s.toCharArray()) {
                map.put(c, map.getOrDefault(c, 0) + 1);
            }
            return map;
        }
        ---
    b.单词频率统计
        ---
        public Map<String, Integer> countWords(String[] words) {
            Map<String, Integer> map = new HashMap<>();
            for (String word : words) {
                map.put(word, map.getOrDefault(word, 0) + 1);
            }
            return map;
        }
        ---

02.去重
    a.数组去重
        ---
        public int[] removeDuplicates(int[] nums) {
            Set<Integer> set = new HashSet<>();
            for (int num : nums) {
                set.add(num);
            }
            return set.stream().mapToInt(Integer::intValue).toArray();
        }
        ---

03.快速查找
    a.两数之和
        ---
        public int[] twoSum(int[] nums, int target) {
            Map<Integer, Integer> map = new HashMap<>();
            for (int i = 0; i < nums.length; i++) {
                int complement = target - nums[i];
                if (map.containsKey(complement)) {
                    return new int[]{map.get(complement), i};
                }
                map.put(nums[i], i);
            }
            return new int[]{};
        }
        ---

04.缓存实现
    a.LRU缓存
        使用HashMap加双向链表实现O(1)时间复杂度的LRU缓存。
    b.简单缓存
        ---
        class Cache {
            private Map<String, Object> cache = new HashMap<>();

            public Object get(String key) {
                return cache.get(key);
            }

            public void put(String key, Object value) {
                cache.put(key, value);
            }
        }
        ---

5.7 经典题目

01.两数之和
    a.题目
        LeetCode 1 - 给定数组和目标值,找出数组中和为目标值的两个数的索引。
    b.哈希表解法
        ---
        public int[] twoSum(int[] nums, int target) {
            Map<Integer, Integer> map = new HashMap<>();
            for (int i = 0; i < nums.length; i++) {
                int complement = target - nums[i];
                if (map.containsKey(complement)) {
                    return new int[]{map.get(complement), i};
                }
                map.put(nums[i], i);
            }
            return new int[]{};
        }
        ---

02.字母异位词分组
    a.题目
        LeetCode 49 - 将字母异位词分组。
    b.哈希表解法
        ---
        public List<List<String>> groupAnagrams(String[] strs) {
            Map<String, List<String>> map = new HashMap<>();
            for (String s : strs) {
                char[] chars = s.toCharArray();
                Arrays.sort(chars);
                String key = new String(chars);
                map.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
            }
            return new ArrayList<>(map.values());
        }
        ---

03.最长连续序列
    a.题目
        LeetCode 128 - 找出数组中最长连续序列的长度。
    b.哈希表解法
        ---
        public int longestConsecutive(int[] nums) {
            Set<Integer> set = new HashSet<>();
            for (int num : nums) {
                set.add(num);
            }

            int maxLen = 0;
            for (int num : set) {
                if (!set.contains(num - 1)) {
                    int currentNum = num;
                    int currentLen = 1;

                    while (set.contains(currentNum + 1)) {
                        currentNum++;
                        currentLen++;
                    }
                    maxLen = Math.max(maxLen, currentLen);
                }
            }
            return maxLen;
        }
        ---

04.有效的字母异位词
    a.题目
        LeetCode 242 - 判断两个字符串是否为字母异位词。
    b.哈希表解法
        ---
        public boolean isAnagram(String s, String t) {
            if (s.length() != t.length()) return false;

            Map<Character, Integer> map = new HashMap<>();
            for (char c : s.toCharArray()) {
                map.put(c, map.getOrDefault(c, 0) + 1);
            }

            for (char c : t.toCharArray()) {
                int count = map.getOrDefault(c, 0);
                if (count == 0) return false;
                map.put(c, count - 1);
            }
            return true;
        }
        ---

6 字符串

6.1 字符串基础

01.字符串定义
    a.Java String
        String是不可变的字符序列,底层使用char数组或byte数组存储。
    b.Go string
        string是不可变的字节序列,底层是byte数组。

02.字符串基本操作
    a.Java实现
        ---
        String s = "hello";

        // 长度
        int len = s.length();

        // 字符访问
        char c = s.charAt(0);

        // 子串
        String sub = s.substring(1, 4);  // "ell"

        // 拼接
        String s2 = s + " world";
        String s3 = s.concat(" world");

        // 比较
        boolean eq = s.equals("hello");
        int cmp = s.compareTo("world");

        // 查找
        int index = s.indexOf('l');      // 2
        int lastIndex = s.lastIndexOf('l');  // 3

        // 替换
        String replaced = s.replace('l', 'L');

        // 分割
        String[] parts = "a,b,c".split(",");
        ---
    b.Go实现
        ---
        s := "hello"

        // 长度
        length := len(s)

        // 字符访问
        b := s[0]  // byte

        // 子串
        sub := s[1:4]  // "ell"

        // 拼接
        s2 := s + " world"

        // 比较
        eq := s == "hello"

        // 查找
        import "strings"
        index := strings.Index(s, "l")

        // 替换
        replaced := strings.Replace(s, "l", "L", -1)

        // 分割
        parts := strings.Split("a,b,c", ",")
        ---

03.StringBuilder和StringBuffer
    a.StringBuilder
        可变字符序列,线程不安全,性能高。
    b.StringBuffer
        可变字符序列,线程安全,性能较低。
    c.使用示例
        ---
        StringBuilder sb = new StringBuilder();
        sb.append("hello");
        sb.append(" ");
        sb.append("world");
        String result = sb.toString();

        // 插入
        sb.insert(5, ",");

        // 删除
        sb.delete(5, 6);

        // 反转
        sb.reverse();
        ---

6.2 字符串匹配

01.暴力匹配
    a.算法思想
        逐个比较文本串和模式串的字符,不匹配时回退。
    b.Java实现
        ---
        public int strStr(String haystack, String needle) {
            if (needle.isEmpty()) return 0;
            int n = haystack.length();
            int m = needle.length();

            for (int i = 0; i <= n - m; i++) {
                int j = 0;
                while (j < m && haystack.charAt(i + j) == needle.charAt(j)) {
                    j++;
                }
                if (j == m) return i;
            }
            return -1;
        }
        ---
    c.时间复杂度
        O(n*m),其中n是文本串长度,m是模式串长度。

02.Rabin-Karp算法
    a.算法思想
        使用哈希函数快速比较子串,只有哈希值相同时才逐字符比较。
    b.滚动哈希
        ---
        public int rabinKarp(String text, String pattern) {
            int n = text.length();
            int m = pattern.length();
            if (m > n) return -1;

            int base = 256;
            int mod = 101;
            int patternHash = 0;
            int textHash = 0;
            int h = 1;

            // 计算h = base^(m-1) % mod
            for (int i = 0; i < m - 1; i++) {
                h = (h * base) % mod;
            }

            // 计算模式串和文本串第一个窗口的哈希值
            for (int i = 0; i < m; i++) {
                patternHash = (base * patternHash + pattern.charAt(i)) % mod;
                textHash = (base * textHash + text.charAt(i)) % mod;
            }

            // 滑动窗口
            for (int i = 0; i <= n - m; i++) {
                if (patternHash == textHash) {
                    if (text.substring(i, i + m).equals(pattern)) {
                        return i;
                    }
                }

                if (i < n - m) {
                    textHash = (base * (textHash - text.charAt(i) * h) +
                               text.charAt(i + m)) % mod;
                    if (textHash < 0) {
                        textHash += mod;
                    }
                }
            }
            return -1;
        }
        ---

6.3 KMP算法

01.KMP算法原理
    a.核心思想
        利用已匹配的信息,避免不必要的回退,通过next数组记录模式串的前缀信息。
    b.时间复杂度
        O(n+m),其中n是文本串长度,m是模式串长度。

02.next数组计算
    a.定义
        next[i]表示模式串前i个字符的最长相等前后缀长度。
    b.Java实现
        ---
        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;
        }
        ---

03.KMP匹配
    a.Java实现
        ---
        public int kmp(String text, String pattern) {
            int n = text.length();
            int m = pattern.length();
            if (m == 0) return 0;

            int[] next = getNext(pattern);
            int j = 0;

            for (int i = 0; i < n; i++) {
                while (j > 0 && text.charAt(i) != pattern.charAt(j)) {
                    j = next[j - 1];
                }
                if (text.charAt(i) == pattern.charAt(j)) {
                    j++;
                }
                if (j == m) {
                    return i - m + 1;
                }
            }
            return -1;
        }
        ---
    b.Go实现
        ---
        func kmp(text, pattern string) int {
            n, m := len(text), len(pattern)
            if m == 0 {
                return 0
            }

            next := getNext(pattern)
            j := 0

            for i := 0; i < n; i++ {
                for j > 0 && text[i] != pattern[j] {
                    j = next[j-1]
                }
                if text[i] == pattern[j] {
                    j++
                }
                if j == m {
                    return i - m + 1
                }
            }
            return -1
        }

        func getNext(pattern string) []int {
            m := len(pattern)
            next := make([]int, m)
            j := 0

            for i := 1; i < m; i++ {
                for j > 0 && pattern[i] != pattern[j] {
                    j = next[j-1]
                }
                if pattern[i] == pattern[j] {
                    j++
                }
                next[i] = j
            }
            return next
        }
        ---

6.4 Trie树

01.Trie树定义
    a.基本概念
        Trie树又称字典树或前缀树,用于高效存储和检索字符串集合。
    b.特点
        根节点不包含字符,每条边代表一个字符,从根到某节点的路径代表一个字符串。
    c.时间复杂度
        插入和查找的时间复杂度为O(m),其中m是字符串长度。

02.Trie树实现
    a.节点定义
        ---
        class TrieNode {
            TrieNode[] children;
            boolean isEnd;

            TrieNode() {
                children = new TrieNode[26];  // 假设只有小写字母
                isEnd = false;
            }
        }
        ---
    b.Trie树类
        ---
        class Trie {
            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.Trie树应用
    a.自动补全
        根据输入前缀,返回所有可能的单词。
    b.拼写检查
        检查单词是否在字典中。
    c.IP路由
        最长前缀匹配。

6.5 字符串处理技巧

01.双指针技巧
    a.反转字符串
        ---
        public void reverseString(char[] s) {
            int left = 0, right = s.length - 1;
            while (left < right) {
                char temp = s[left];
                s[left] = s[right];
                s[right] = temp;
                left++;
                right--;
            }
        }
        ---
    b.回文判断
        ---
        public boolean isPalindrome(String s) {
            int left = 0, right = s.length() - 1;
            while (left < right) {
                if (s.charAt(left) != s.charAt(right)) {
                    return false;
                }
                left++;
                right--;
            }
            return true;
        }
        ---

02.滑动窗口
    a.最长无重复子串
        ---
        public int lengthOfLongestSubstring(String s) {
            Set<Character> set = new HashSet<>();
            int maxLen = 0;
            int left = 0;

            for (int right = 0; right < s.length(); right++) {
                while (set.contains(s.charAt(right))) {
                    set.remove(s.charAt(left));
                    left++;
                }
                set.add(s.charAt(right));
                maxLen = Math.max(maxLen, right - left + 1);
            }
            return maxLen;
        }
        ---

03.字符统计
    a.字符数组统计
        ---
        public boolean canConstruct(String ransomNote, String magazine) {
            int[] count = new int[26];
            for (char c : magazine.toCharArray()) {
                count[c - 'a']++;
            }
            for (char c : ransomNote.toCharArray()) {
                if (--count[c - 'a'] < 0) {
                    return false;
                }
            }
            return true;
        }
        ---

6.6 经典题目

01.反转字符串中的单词
    a.题目
        LeetCode 151 - 反转字符串中的单词顺序。
    b.Java解法
        ---
        public String reverseWords(String s) {
            String[] words = s.trim().split("\\s+");
            StringBuilder sb = new StringBuilder();
            for (int i = words.length - 1; i >= 0; i--) {
                sb.append(words[i]);
                if (i > 0) sb.append(" ");
            }
            return sb.toString();
        }
        ---

02.最长回文子串
    a.题目
        LeetCode 5 - 找出字符串中最长的回文子串。
    b.中心扩展法
        ---
        public String longestPalindrome(String s) {
            if (s == null || s.length() < 1) return "";
            int start = 0, end = 0;

            for (int i = 0; i < s.length(); i++) {
                int len1 = expandAroundCenter(s, i, i);
                int len2 = expandAroundCenter(s, i, i + 1);
                int len = Math.max(len1, len2);
                if (len > end - start) {
                    start = i - (len - 1) / 2;
                    end = i + len / 2;
                }
            }
            return s.substring(start, end + 1);
        }

        private int expandAroundCenter(String s, int left, int right) {
            while (left >= 0 && right < s.length() &&
                   s.charAt(left) == s.charAt(right)) {
                left--;
                right++;
            }
            return right - left - 1;
        }
        ---

03.字符串相加
    a.题目
        LeetCode 415 - 实现大数相加。
    b.Java解法
        ---
        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();
        }
        ---

04.实现strStr
    a.题目
        LeetCode 28 - 实现字符串匹配函数。
    b.KMP解法
        使用KMP算法实现高效的字符串匹配。

7 综合实战

7.1 LeetCode精选题目

01.数组类题目
    a.两数之和
        LeetCode 1 - 使用哈希表,时间复杂度O(n)。
    b.三数之和
        LeetCode 15 - 排序加双指针,时间复杂度O(n²)。
    c.合并区间
        LeetCode 56 - 排序后合并重叠区间。
    d.旋转数组
        LeetCode 189 - 三次反转法。

02.链表类题目
    a.反转链表
        LeetCode 206 - 迭代或递归实现。
    b.合并两个有序链表
        LeetCode 21 - 归并思想。
    c.环形链表
        LeetCode 141 - 快慢指针检测环。
    d.相交链表
        LeetCode 160 - 双指针消除长度差。

03.栈队列类题目
    a.有效的括号
        LeetCode 20 - 使用栈匹配括号。
    b.最小栈
        LeetCode 155 - 使用辅助栈存储最小值。
    c.滑动窗口最大值
        LeetCode 239 - 使用单调队列。
    d.每日温度
        LeetCode 739 - 使用单调栈。

04.树类题目
    a.二叉树的最大深度
        LeetCode 104 - 递归或层序遍历。
    b.对称二叉树
        LeetCode 101 - 递归判断镜像。
    c.二叉树的层序遍历
        LeetCode 102 - BFS实现。
    d.二叉树的最近公共祖先
        LeetCode 236 - 递归查找。

7.2 数据结构选择策略

01.根据操作特点选择
    a.频繁随机访问
        选择数组或ArrayList,时间复杂度O(1)。
    b.频繁插入删除
        选择链表或LinkedList,时间复杂度O(1)。
    c.需要快速查找
        选择哈希表HashMap或HashSet,平均时间复杂度O(1)。
    d.需要有序存储
        选择TreeMap或TreeSet,基于红黑树,时间复杂度O(log n)。

02.根据数据特点选择
    a.固定大小
        使用数组,空间连续,缓存友好。
    b.动态大小
        使用ArrayList或LinkedList,支持动态扩展。
    c.键值对存储
        使用HashMap或TreeMap,根据是否需要有序选择。
    d.去重需求
        使用HashSet或TreeSet,自动去重。

03.根据性能要求选择
    a.时间优先
        选择哈希表,牺牲空间换取时间。
    b.空间优先
        选择链表或树,按需分配空间。
    c.平衡考虑
        选择红黑树,时间和空间都较优。

04.常见场景推荐
    a.缓存实现
        HashMap + 双向链表实现LRU缓存。
    b.优先级队列
        使用PriorityQueue,基于堆实现。
    c.滑动窗口
        使用Deque实现单调队列。
    d.前缀匹配
        使用Trie树实现高效前缀查询。

7.3 常见面试题

01.设计题
    a.LRU缓存
        ---
        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;
            }
        }
        ---

02.算法题
    a.Top K问题
        ---
        public int[] topKFrequent(int[] nums, int k) {
            Map<Integer, Integer> map = new HashMap<>();
            for (int num : nums) {
                map.put(num, map.getOrDefault(num, 0) + 1);
            }

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

            for (int num : map.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;
        }
        ---

03.综合题
    a.克隆图
        LeetCode 133 - 使用HashMap和DFS/BFS克隆图。
    b.单词搜索
        LeetCode 79 - 使用DFS和回溯在二维网格中搜索单词。

7.4 性能优化技巧

01.时间优化
    a.使用哈希表
        将O(n)的查找优化为O(1)。
    b.使用双指针
        将O(n²)的暴力法优化为O(n)。
    c.使用单调栈队列
        优化滑动窗口问题。
    d.预处理
        使用前缀和、差分数组等预处理技巧。

02.空间优化
    a.原地算法
        在原数组上操作,避免额外空间。
    b.滚动数组
        动态规划中只保留必要的状态。
    c.位运算
        使用位运算代替数组存储状态。

03.代码优化
    a.避免重复计算
        使用缓存或记忆化搜索。
    b.提前终止
        满足条件时立即返回。
    c.选择合适的数据结构
        根据场景选择最优的数据结构。

04.Java性能技巧
    a.StringBuilder
        字符串拼接使用StringBuilder而不是+。
    b.ArrayList预分配
        已知大小时使用带容量的构造函数。
    c.避免装箱拆箱
        使用基本类型而不是包装类。

7.5 实战项目案例

01.实现一个简单的内存数据库
    a.需求分析
        支持set、get、delete、count等操作,支持事务。
    b.数据结构选择
        使用HashMap存储键值对,使用Stack存储事务状态。
    c.核心代码
        ---
        class SimpleDB {
            private Map<String, String> data;
            private Stack<Map<String, String>> transactions;

            public SimpleDB() {
                data = new HashMap<>();
                transactions = new Stack<>();
            }

            public void set(String key, String value) {
                data.put(key, value);
            }

            public String get(String key) {
                return data.get(key);
            }

            public void delete(String key) {
                data.remove(key);
            }

            public int count(String value) {
                int cnt = 0;
                for (String v : data.values()) {
                    if (v.equals(value)) cnt++;
                }
                return cnt;
            }

            public void begin() {
                transactions.push(new HashMap<>(data));
            }

            public void commit() {
                if (!transactions.isEmpty()) {
                    transactions.pop();
                }
            }

            public void rollback() {
                if (!transactions.isEmpty()) {
                    data = transactions.pop();
                }
            }
        }
        ---

02.实现一个文件系统
    a.需求分析
        支持创建目录、创建文件、读写文件、列出目录内容。
    b.数据结构选择
        使用Trie树存储目录结构,每个节点包含子节点Map和文件内容。
    c.核心功能
        mkdir、addFile、readFile、ls等操作。

03.实现一个任务调度器
    a.需求分析
        支持添加任务、执行任务、取消任务,任务有优先级。
    b.数据结构选择
        使用PriorityQueue存储待执行任务,按优先级排序。
    c.核心功能
        addTask、executeNext、cancelTask等操作。

04.学习建议
    a.多做项目
        将数据结构应用到实际项目中,加深理解。
    b.阅读源码
        阅读JDK集合框架源码,学习优秀实现。
    c.持续刷题
        保持每天刷题的习惯,巩固知识点。
    d.总结归纳
        定期总结常见题型和解题模板。