🧮 常用算法大全

掌握核心算法,提升编程思维与问题解决能力

📑 算法分类体系

按照算法类型和应用场景分类,系统学习各类经典算法

🔄

排序算法

将数据按特定顺序排列的基础算法。

冒泡排序 O(n²)
快速排序 ⭐ O(n log n)
归并排序 O(n log n)
堆排序 O(n log n)
计数排序 O(n+k)
🔍

搜索算法

在数据中查找目标的高效方法。

• 线性搜索(顺序查找)
• 二分搜索 ⭐
• 深度优先搜索(DFS)
• 广度优先搜索(BFS)
• A*搜索算法
🎯

动态规划

通过保存子问题解来优化递归的强大技术。

• 斐波那契数列
• 背包问题 ⭐
• 最长公共子序列
• 最短路径(Dijkstra)
• 状态压缩DP
🎲

贪心算法

每步选择局部最优,追求全局最优解。

• 活动选择问题
• 哈夫曼编码
• 最小生成树(Prim/Kruskal)
• 区间调度问题
• 找零钱问题
✂️

分治算法

分而治之,将大问题分解为小问题求解。

• 归并排序
• 快速排序
• 二分搜索
• 大整数乘法(Karatsuba)
• 最近点对问题
🔙

回溯算法

通过试错和回退来寻找所有可能的解。

• N皇后问题 ⭐
• 全排列
• 子集生成
• 数独求解
• 迷宫问题
🕸️

图算法

处理节点和边关系的专门算法。

• 深度优先遍历(DFS)
• 广度优先遍历(BFS)
• 最短路径(Dijkstra)⭐
• 拓扑排序
• 最小生成树
📝

字符串算法

处理文本和字符串的专用算法。

• KMP字符串匹配 ⭐
• 字符串哈希
• 最长公共子串
• 编辑距离
• 字典树(Trie)
🔢

数学算法

数论、组合数学相关的算法。

• 欧几里得算法(GCD)
• 快速幂运算
• 素数筛法
• 组合数计算
• 矩阵快速幂

🎯 LeetCode 刷题路线

新手村

0-50题:建立信心

推荐题目类型:

  • ✓ 数组基础:Two Sum、最大子数组
  • ✓ 字符串:回文判断、字符统计
  • ✓ 链表:反转链表、合并链表
  • ✓ 简单数学:阶乘、素数判断

学习重点:

  • → 熟悉基本语法和API
  • → 理解时间复杂度概念
  • → 养成测试用例思维
  • → 目标:简单题正确率90%+
进阶区

50-150题:系统学习

推荐题目类型:

  • ✓ 二叉树:遍历、BST、LCA
  • ✓ 栈和队列:括号匹配、滑动窗口
  • ✓ 哈希表:Two Sum变种
  • ✓ 双指针:三数之和、盛水容器

学习重点:

  • → 掌握常见数据结构特性
  • → 学会选择合适的数据结构
  • → 理解递归与迭代转换
  • → 目标:中等题正确率60%+
高手区

150-300题:融会贯通

推荐题目类型:

  • ✓ 动态规划:背包、股票、打家劫舍
  • ✓ 回溯:N皇后、组合总和
  • ✓ 图算法:岛屿数量、课程表
  • ✓ 高级数据结构:并查集、前缀树

学习重点:

  • → 熟练运用DP思想
  • → 掌握各种优化技巧
  • → 培养算法设计能力
  • → 目标:中等题正确率80%+
竞赛级

300+题:冲刺顶尖

推荐题目类型:

  • ✓ 困难DP:区间DP、树形DP
  • ✓ 线段树、树状数组
  • ✓ 字符串高级:后缀数组、AC自动机
  • ✓ 计算几何、博弈论

学习重点:

  • → 深度理解算法本质
  • → 能独立设计新算法
  • → 参加算法竞赛
  • → 目标:困难题正确率50%+

⭐ 经典算法详解与代码实现

包含Python、Go、Java、C++四种语言的完整实现,时间复杂度分析,应用场景说明

1. 快速排序(QuickSort)

分治思想的经典应用 | 平均O(n log n) | 不稳定排序

📚 算法原理

核心思想:选择一个基准元素(pivot),将数组分为两部分:小于基准的放左边,大于基准的放右边,然后递归处理两部分。

步骤: 1. 选择基准元素 → 2. 分区(Partition) → 3. 递归排序左右子数组

最好情况

O(n log n)

每次平分

平均情况

O(n log n)

随机基准

最坏情况

O(n²)

已排序

Python 最简洁的实现
def quicksort(arr):
    """快速排序 - Python实现"""
    if len(arr) <= 1:
        return arr
    
    # 选择基准(取中间元素)
    pivot = arr[len(arr) // 2]
    
    # 分区
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    
    # 递归排序
    return quicksort(left) + middle + quicksort(right)

# 使用示例
arr = [3, 6, 8, 10, 1, 2, 1]
sorted_arr = quicksort(arr)
print(sorted_arr)  # [1, 1, 2, 3, 6, 8, 10]

# ========== 原地排序版本(节省空间) ==========
def quicksort_inplace(arr, low, high):
    """原地快速排序"""
    if low < high:
        # 分区操作
        pi = partition(arr, low, high)
        
        # 递归排序子数组
        quicksort_inplace(arr, low, pi - 1)
        quicksort_inplace(arr, pi + 1, high)

def partition(arr, low, high):
    """分区函数"""
    pivot = arr[high]  # 选择最右边为基准
    i = low - 1
    
    for j in range(low, high):
        if arr[j] <= pivot:
            i += 1
            arr[i], arr[j] = arr[j], arr[i]
    
    arr[i + 1], arr[high] = arr[high], arr[i + 1]
    return i + 1

# 使用
arr = [10, 7, 8, 9, 1, 5]
quicksort_inplace(arr, 0, len(arr) - 1)
print(arr)  # [1, 5, 7, 8, 9, 10]
Go 高性能实现
package main

import "fmt"

func quickSort(arr []int) []int {
    if len(arr) <= 1 {
        return arr
    }
    
    pivot := arr[len(arr)/2]
    var left, middle, right []int
    
    for _, v := range arr {
        switch {
        case v < pivot:
            left = append(left, v)
        case v == pivot:
            middle = append(middle, v)
        case v > pivot:
            right = append(right, v)
        }
    }
    
    left = quickSort(left)
    right = quickSort(right)
    
    return append(append(left, middle...), right...)
}

// 原地排序版本
func quickSortInPlace(arr []int, low, high int) {
    if low < high {
        pi := partition(arr, low, high)
        quickSortInPlace(arr, low, pi-1)
        quickSortInPlace(arr, pi+1, high)
    }
}

func partition(arr []int, low, high int) int {
    pivot := arr[high]
    i := low - 1
    
    for j := low; j < high; j++ {
        if arr[j] <= pivot {
            i++
            arr[i], arr[j] = arr[j], arr[i]
        }
    }
    
    arr[i+1], arr[high] = arr[high], arr[i+1]
    return i + 1
}

func main() {
    arr := []int{3, 6, 8, 10, 1, 2, 1}
    sorted := quickSort(arr)
    fmt.Println(sorted) // [1 1 2 3 6 8 10]
}
Java 经典面向对象实现
public class QuickSort {
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pi = partition(arr, low, high);
            quickSort(arr, low, pi - 1);
            quickSort(arr, pi + 1, high);
        }
    }
    
    private static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = low - 1;
        
        for (int j = low; j < high; j++) {
            if (arr[j] <= pivot) {
                i++;
                // 交换
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        
        // 将基准放到正确位置
        int temp = arr[i + 1];
        arr[i + 1] = arr[high];
        arr[high] = temp;
        
        return i + 1;
    }
    
    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        quickSort(arr, 0, arr.length - 1);
        
        // 打印结果
        for (int num : arr) {
            System.out.print(num + " ");
        }
        // 输出: 1 5 7 8 9 10
    }
}
C++ STL标准库实现
#include <iostream>
#include <vector>
using namespace std;

int partition(vector<int>& arr, int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    
    for (int j = low; j < high; j++) {
        if (arr[j] <= pivot) {
            i++;
            swap(arr[i], arr[j]);
        }
    }
    
    swap(arr[i + 1], arr[high]);
    return i + 1;
}

void quickSort(vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        
        quickSort(arr, low, pi - 1);
        quickSort(arr, pi + 1, high);
    }
}

int main() {
    vector<int> arr = {10, 7, 8, 9, 1, 5};
    quickSort(arr, 0, arr.size() - 1);
    
    for (int num : arr) {
        cout << num << " ";
    }
    // 输出: 1 5 7 8 9 10
    
    return 0;
}

💡 优化技巧

  • 三数取中:选择第一个、中间、最后一个的中位数作为基准
  • 随机化:随机选择基准避免最坏情况
  • 小数组优化:当子数组很小时(<10),切换到插入排序
  • 三路快排:处理大量重复元素时更高效

3. DFS/BFS 图遍历算法

图和树的核心遍历算法 | O(V+E) | V=顶点数 E=边数

📚 DFS - 深度优先搜索

核心思想:尽可能深地搜索分支,直到无路可走再回溯。类似"走迷宫沿着一条路走到底"。

Python DFS实现(递归+迭代)
# ========== DFS递归实现 ==========
def dfs_recursive(graph, node, visited=None):
    """DFS递归实现"""
    if visited is None:
        visited = set()
    
    visited.add(node)
    print(node, end=' ')
    
    # 访问所有未访问的邻居
    for neighbor in graph[node]:
        if neighbor not in visited:
            dfs_recursive(graph, neighbor, visited)
    
    return visited

# ========== DFS迭代实现(使用栈) ==========
def dfs_iterative(graph, start):
    """DFS迭代实现"""
    visited = set()
    stack = [start]
    
    while stack:
        node = stack.pop()  # 栈:后进先出
        
        if node not in visited:
            visited.add(node)
            print(node, end=' ')
            
            # 将未访问的邻居压入栈
            for neighbor in reversed(graph[node]):  # 反转保证顺序
                if neighbor not in visited:
                    stack.append(neighbor)
    
    return visited

# ========== BFS广度优先搜索(使用队列) ==========
from collections import deque

def bfs(graph, start):
    """BFS实现"""
    visited = set([start])
    queue = deque([start])
    
    while queue:
        node = queue.popleft()  # 队列:先进先出
        print(node, end=' ')
        
        for neighbor in graph[node]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)
    
    return visited

# 使用示例
graph = {
    'A': ['B', 'C'],
    'B': ['A', 'D', 'E'],
    'C': ['A', 'F'],
    'D': ['B'],
    'E': ['B', 'F'],
    'F': ['C', 'E']
}

print("DFS递归:", end=' ')
dfs_recursive(graph, 'A')  # A B D E F C

print("\nDFS迭代:", end=' ')
dfs_iterative(graph, 'A')  # A B D E F C

print("\nBFS:", end=' ')
bfs(graph, 'A')  # A B C D E F (层次遍历)
Go
package main

import "fmt"

type Graph map[string][]string

// DFS递归实现
func DFSRecursive(graph Graph, node string, visited map[string]bool) {
    visited[node] = true
    fmt.Print(node, " ")
    
    for _, neighbor := range graph[node] {
        if !visited[neighbor] {
            DFSRecursive(graph, neighbor, visited)
        }
    }
}

// BFS实现
func BFS(graph Graph, start string) {
    visited := make(map[string]bool)
    queue := []string{start}
    visited[start] = true
    
    for len(queue) > 0 {
        node := queue[0]
        queue = queue[1:]  // 出队
        fmt.Print(node, " ")
        
        for _, neighbor := range graph[node] {
            if !visited[neighbor] {
                visited[neighbor] = true
                queue = append(queue, neighbor)
            }
        }
    }
}

func main() {
    graph := Graph{
        "A": {"B", "C"},
        "B": {"A", "D", "E"},
        "C": {"A", "F"},
        "D": {"B"},
        "E": {"B", "F"},
        "F": {"C", "E"},
    }
    
    fmt.Print("DFS: ")
    DFSRecursive(graph, "A", make(map[string]bool))
    
    fmt.Print("\nBFS: ")
    BFS(graph, "A")
}

🎯 DFS应用场景

  • • 路径查找(所有路径)
  • • 拓扑排序
  • • 判断连通性
  • • 检测环

🎯 BFS应用场景

  • • 最短路径(无权图)
  • • 层次遍历
  • • 最少步数问题
  • • 连通分量数量

4. 动态规划(Dynamic Programming)

最优子结构 + 重叠子问题 | 空间换时间

📚 算法原理

核心思想:将复杂问题分解为子问题,保存子问题的解避免重复计算。

关键要素:
1. 最优子结构
2. 重叠子问题
解决步骤:
1. 定义状态
2. 状态转移方程
3. 初始化
4. 遍历顺序
Python 斐波那契 + 0-1背包问题
# ========== 示例1:斐波那契数列 ==========
# 暴力递归(指数级时间,很慢)
def fib_naive(n):
    if n <= 1:
        return n
    return fib_naive(n-1) + fib_naive(n-2)

# DP优化(线性时间)
def fib_dp(n):
    if n <= 1:
        return n
    
    dp = [0] * (n + 1)
    dp[1] = 1
    
    for i in range(2, n + 1):
        dp[i] = dp[i-1] + dp[i-2]
    
    return dp[n]

# 空间优化(只需两个变量)
def fib_optimized(n):
    if n <= 1:
        return n
    
    prev, curr = 0, 1
    for _ in range(2, n + 1):
        prev, curr = curr, prev + curr
    
    return curr

print(fib_dp(10))  # 55

# ========== 示例2:0-1背包问题 ==========
def knapsack(weights, values, capacity):
    """
    0-1背包:每个物品只能选一次
    weights: 物品重量列表
    values: 物品价值列表
    capacity: 背包容量
    """
    n = len(weights)
    
    # dp[i][w]表示前i个物品,容量为w时的最大价值
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]
    
    for i in range(1, n + 1):
        for w in range(1, capacity + 1):
            # 不选第i个物品
            dp[i][w] = dp[i-1][w]
            
            # 选第i个物品(如果放得下)
            if weights[i-1] <= w:
                dp[i][w] = max(
                    dp[i][w],
                    dp[i-1][w - weights[i-1]] + values[i-1]
                )
    
    return dp[n][capacity]

# 使用
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
capacity = 8
max_value = knapsack(weights, values, capacity)
print(f"最大价值: {max_value}")  # 10

# ========== 示例3:最长公共子序列(LCS) ==========
def longest_common_subsequence(text1, text2):
    """最长公共子序列"""
    m, n = len(text1), len(text2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if text1[i-1] == text2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    
    return dp[m][n]

lcs_len = longest_common_subsequence("abcde", "ace")
print(f"LCS长度: {lcs_len}")  # 3 (ace)
Go
package main

import "fmt"

// 0-1背包问题
func knapsack(weights, values []int, capacity int) int {
    n := len(weights)
    dp := make([][]int, n+1)
    for i := range dp {
        dp[i] = make([]int, capacity+1)
    }
    
    for i := 1; i <= n; i++ {
        for w := 1; w <= capacity; w++ {
            dp[i][w] = dp[i-1][w]
            
            if weights[i-1] <= w {
                dp[i][w] = max(
                    dp[i][w],
                    dp[i-1][w-weights[i-1]] + values[i-1],
                )
            }
        }
    }
    
    return dp[n][capacity]
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func main() {
    weights := []int{2, 3, 4, 5}
    values := []int{3, 4, 5, 6}
    capacity := 8
    
    maxValue := knapsack(weights, values, capacity)
    fmt.Printf("最大价值: %d\n", maxValue)
}

🎯 DP经典问题

序列DP

最长递增子序列、编辑距离

背包DP

0-1背包、完全背包、多重背包

区间DP

矩阵链乘、石子合并

树形DP

树的最大独立集、树的直径

状态压缩DP

旅行商问题TSP

数位DP

统计特定数字

5. 回溯算法(Backtracking)

穷举所有可能 + 剪枝优化 | 指数时间复杂度

Python N皇后 + 全排列 + 子集
# ========== 示例1:N皇后问题 ==========
def solve_n_queens(n):
    """N皇后问题"""
    result = []
    board = [['.'] * n for _ in range(n)]
    
    def is_valid(row, col):
        # 检查列
        for i in range(row):
            if board[i][col] == 'Q':
                return False
        
        # 检查左上对角线
        i, j = row - 1, col - 1
        while i >= 0 and j >= 0:
            if board[i][j] == 'Q':
                return False
            i, j = i - 1, j - 1
        
        # 检查右上对角线
        i, j = row - 1, col + 1
        while i >= 0 and j < n:
            if board[i][j] == 'Q':
                return False
            i, j = i - 1, j + 1
        
        return True
    
    def backtrack(row):
        if row == n:
            # 找到一个解
            result.append([''.join(row) for row in board])
            return
        
        for col in range(n):
            if is_valid(row, col):
                board[row][col] = 'Q'  # 做选择
                backtrack(row + 1)     # 进入下一层
                board[row][col] = '.'  # 撤销选择(回溯)
    
    backtrack(0)
    return result

# 使用
solutions = solve_n_queens(4)
print(f"4皇后有{len(solutions)}个解")

# ========== 示例2:全排列 ==========
def permute(nums):
    """生成数组的所有排列"""
    result = []
    
    def backtrack(path, remaining):
        if not remaining:
            result.append(path[:])
            return
        
        for i in range(len(remaining)):
            path.append(remaining[i])
            backtrack(path, remaining[:i] + remaining[i+1:])
            path.pop()  # 回溯
    
    backtrack([], nums)
    return result

perms = permute([1, 2, 3])
print(f"全排列数量: {len(perms)}")  # 6

# ========== 示例3:子集生成 ==========
def subsets(nums):
    """生成所有子集"""
    result = []
    
    def backtrack(start, path):
        result.append(path[:])
        
        for i in range(start, len(nums)):
            path.append(nums[i])
            backtrack(i + 1, path)
            path.pop()
    
    backtrack(0, [])
    return result

all_subsets = subsets([1, 2, 3])
print(f"子集数量: {len(all_subsets)}")  # 8

💡 回溯算法通用模板

result = []

def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    
    for 选择 in 选择列表:
        # 做选择
        路径.add(选择)
        
        # 进入下一层决策树
        backtrack(路径, 新的选择列表)
        
        # 撤销选择(回溯)
        路径.remove(选择)

6. 滑动窗口(Sliding Window)

处理连续子数组 | O(n) | 双指针变种

Python 无重复字符的最长子串
# ========== 无重复字符的最长子串 ==========
def length_of_longest_substring(s):
    """
    LeetCode 3: 无重复字符的最长子串
    输入: "abcabcbb"
    输出: 3 ("abc")
    """
    char_set = set()
    left = 0
    max_len = 0
    
    for right in range(len(s)):
        # 如果有重复,移动左指针
        while s[right] in char_set:
            char_set.remove(s[left])
            left += 1
        
        char_set.add(s[right])
        max_len = max(max_len, right - left + 1)
    
    return max_len

print(length_of_longest_substring("abcabcbb"))  # 3

# ========== 最小覆盖子串 ==========
from collections import Counter

def min_window(s, t):
    """
    LeetCode 76: 最小覆盖子串
    输入: s = "ADOBECODEBANC", t = "ABC"
    输出: "BANC"
    """
    if not s or not t:
        return ""
    
    need = Counter(t)
    window = {}
    left = right = 0
    valid = 0  # 窗口中满足need条件的字符个数
    
    # 记录最小子串的起始位置和长度
    start = 0
    min_len = float('inf')
    
    while right < len(s):
        # 扩大窗口
        c = s[right]
        right += 1
        
        if c in need:
            window[c] = window.get(c, 0) + 1
            if window[c] == need[c]:
                valid += 1
        
        # 收缩窗口
        while valid == len(need):
            # 更新最小子串
            if right - left < min_len:
                start = left
                min_len = right - left
            
            d = s[left]
            left += 1
            
            if d in need:
                if window[d] == need[d]:
                    valid -= 1
                window[d] -= 1
    
    return "" if min_len == float('inf') else s[start:start + min_len]

print(min_window("ADOBECODEBANC", "ABC"))  # "BANC"

7. 双指针(Two Pointers)

一次遍历解决问题 | O(n) | 空间O(1)

Python 三数之和 + 盛水容器
# ========== 两数之和(有序数组) ==========
def two_sum_sorted(numbers, target):
    """
    输入: numbers = [2,7,11,15], target = 9
    输出: [0, 1]  # numbers[0] + numbers[1] = 9
    """
    left, right = 0, len(numbers) - 1
    
    while left < right:
        current_sum = numbers[left] + numbers[right]
        
        if current_sum == target:
            return [left, right]
        elif current_sum < target:
            left += 1  # 和太小,左指针右移
        else:
            right -= 1  # 和太大,右指针左移
    
    return []

# ========== 三数之和 ==========
def three_sum(nums):
    """
    找出所有和为0的三元组
    输入: [-1,0,1,2,-1,-4]
    输出: [[-1,-1,2],[-1,0,1]]
    """
    nums.sort()  # 先排序
    result = []
    
    for i in range(len(nums) - 2):
        # 跳过重复元素
        if i > 0 and nums[i] == nums[i-1]:
            continue
        
        # 转化为两数之和
        left, right = i + 1, len(nums) - 1
        target = -nums[i]
        
        while left < right:
            current_sum = nums[left] + nums[right]
            
            if current_sum == target:
                result.append([nums[i], nums[left], nums[right]])
                
                # 跳过重复
                while left < right and nums[left] == nums[left + 1]:
                    left += 1
                while left < right and nums[right] == nums[right - 1]:
                    right -= 1
                
                left += 1
                right -= 1
            elif current_sum < target:
                left += 1
            else:
                right -= 1
    
    return result

# ========== 盛最多水的容器 ==========
def max_area(height):
    """
    双指针贪心:每次移动高度较小的那一边
    """
    left, right = 0, len(height) - 1
    max_water = 0
    
    while left < right:
        width = right - left
        area = min(height[left], height[right]) * width
        max_water = max(max_water, area)
        
        # 移动高度较小的指针
        if height[left] < height[right]:
            left += 1
        else:
            right -= 1
    
    return max_water

8. 并查集(Union-Find)

处理集合合并与查询 | 接近O(1) | 路径压缩

Python
class UnionFind:
    """并查集(带路径压缩和按秩合并)"""
    
    def __init__(self, n):
        self.parent = list(range(n))  # 每个节点的父节点
        self.rank = [0] * n           # 树的高度
        self.count = n                # 连通分量数量
    
    def find(self, x):
        """查找根节点(带路径压缩)"""
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])  # 路径压缩
        return self.parent[x]
    
    def union(self, x, y):
        """合并两个集合(按秩合并)"""
        root_x = self.find(x)
        root_y = self.find(y)
        
        if root_x == root_y:
            return False  # 已在同一集合
        
        # 按秩合并:将矮树连到高树
        if self.rank[root_x] < self.rank[root_y]:
            self.parent[root_x] = root_y
        elif self.rank[root_x] > self.rank[root_y]:
            self.parent[root_y] = root_x
        else:
            self.parent[root_y] = root_x
            self.rank[root_x] += 1
        
        self.count -= 1
        return True
    
    def connected(self, x, y):
        """判断是否连通"""
        return self.find(x) == self.find(y)

# ========== 应用:朋友圈数量 ==========
def find_circle_num(is_connected):
    """
    LeetCode 547: 省份数量
    """
    n = len(is_connected)
    uf = UnionFind(n)
    
    for i in range(n):
        for j in range(i + 1, n):
            if is_connected[i][j] == 1:
                uf.union(i, j)
    
    return uf.count

# 使用
matrix = [
    [1, 1, 0],
    [1, 1, 0],
    [0, 0, 1]
]
print(f"朋友圈数量: {find_circle_num(matrix)}")  # 2
Go
package main

import "fmt"

type UnionFind struct {
    parent []int
    rank   []int
    count  int
}

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

func (uf *UnionFind) Find(x int) int {
    if uf.parent[x] != x {
        uf.parent[x] = uf.Find(uf.parent[x])  // 路径压缩
    }
    return uf.parent[x]
}

func (uf *UnionFind) Union(x, y int) bool {
    rootX, rootY := uf.Find(x), uf.Find(y)
    
    if rootX == rootY {
        return false
    }
    
    // 按秩合并
    if uf.rank[rootX] < uf.rank[rootY] {
        uf.parent[rootX] = rootY
    } else if uf.rank[rootX] > uf.rank[rootY] {
        uf.parent[rootY] = rootX
    } else {
        uf.parent[rootY] = rootX
        uf.rank[rootX]++
    }
    
    uf.count--
    return true
}

func main() {
    uf := NewUnionFind(10)
    uf.Union(0, 1)
    uf.Union(1, 2)
    fmt.Printf("连通分量数: %d\n", uf.count)
}

9. 贪心算法(Greedy Algorithm)

局部最优 → 全局最优 | 需要证明贪心性质

Python 区间调度 + 跳跃游戏
# ========== 区间调度问题 ==========
def interval_schedule(intervals):
    """
    给定多个活动,每次只能参加一个,求最多能参加几个
    输入: [[1,3], [2,4], [3,5]]
    输出: 2
    """
    if not intervals:
        return 0
    
    # 按结束时间排序(贪心策略)
    intervals.sort(key=lambda x: x[1])
    
    count = 1
    end = intervals[0][1]
    
    for i in range(1, len(intervals)):
        # 如果当前活动开始时间>=上个活动结束时间
        if intervals[i][0] >= end:
            count += 1
            end = intervals[i][1]
    
    return count

# ========== 跳跃游戏II(最少跳跃次数) ==========
def jump(nums):
    """
    每次跳最远能跳到的位置
    """
    n = len(nums)
    jumps = 0
    current_end = 0
    farthest = 0
    
    for i in range(n - 1):
        farthest = max(farthest, i + nums[i])
        
        # 到达当前跳跃的边界
        if i == current_end:
            jumps += 1
            current_end = farthest
    
    return jumps

print(jump([2,3,1,1,4]))  # 2 (跳1步到索引1,再跳1步到末尾)

10. KMP字符串匹配

高效字符串匹配 | O(m+n) | 部分匹配表

Python
def kmp_search(text, pattern):
    """
    KMP字符串匹配算法
    时间复杂度:O(m + n)
    """
    def build_lps(pattern):
        """构建最长公共前后缀数组(LPS)"""
        lps = [0] * len(pattern)
        length = 0
        i = 1
        
        while i < len(pattern):
            if pattern[i] == pattern[length]:
                length += 1
                lps[i] = length
                i += 1
            else:
                if length != 0:
                    length = lps[length - 1]
                else:
                    lps[i] = 0
                    i += 1
        
        return lps
    
    if not pattern:
        return 0
    
    lps = build_lps(pattern)
    i = j = 0  # i指向text,j指向pattern
    
    while i < len(text):
        if text[i] == pattern[j]:
            i += 1
            j += 1
        
        if j == len(pattern):
            return i - j  # 找到匹配
        elif i < len(text) and text[i] != pattern[j]:
            if j != 0:
                j = lps[j - 1]
            else:
                i += 1
    
    return -1  # 未找到

# 使用
text = "ABABDABACDABABCABAB"
pattern = "ABABCABAB"
index = kmp_search(text, pattern)
print(f"匹配位置: {index}")  # 10
Go
package main

import "fmt"

func buildLPS(pattern string) []int {
    lps := make([]int, len(pattern))
    length := 0
    i := 1
    
    for i < len(pattern) {
        if pattern[i] == pattern[length] {
            length++
            lps[i] = length
            i++
        } else {
            if length != 0 {
                length = lps[length-1]
            } else {
                lps[i] = 0
                i++
            }
        }
    }
    
    return lps
}

func kmpSearch(text, pattern string) int {
    if len(pattern) == 0 {
        return 0
    }
    
    lps := buildLPS(pattern)
    i, j := 0, 0
    
    for i < len(text) {
        if text[i] == pattern[j] {
            i++
            j++
        }
        
        if j == len(pattern) {
            return i - j
        } else if i < len(text) && text[i] != pattern[j] {
            if j != 0 {
                j = lps[j-1]
            } else {
                i++
            }
        }
    }
    
    return -1
}

func main() {
    text := "ABABDABACDABABCABAB"
    pattern := "ABABCABAB"
    index := kmpSearch(text, pattern)
    fmt.Printf("匹配位置: %d\n", index)
}

📊 算法复杂度对比总结

算法 时间复杂度 空间复杂度 典型应用 难度
快速排序 O(n log n) O(log n) 大数据排序 ⭐⭐⭐
二分搜索 O(log n) O(1) 有序数组查找 ⭐⭐
DFS/BFS O(V+E) O(V) 图遍历、路径查找 ⭐⭐⭐
动态规划 O(n²)-O(n³) O(n²) 最优化问题 ⭐⭐⭐⭐⭐
回溯算法 O(2ⁿ)-O(n!) O(n) 组合、排列问题 ⭐⭐⭐⭐
滑动窗口 O(n) O(1)-O(k) 连续子数组 ⭐⭐⭐
双指针 O(n) O(1) 数组、链表操作 ⭐⭐
并查集 接近O(1) O(n) 连通性判断 ⭐⭐⭐
KMP匹配 O(m+n) O(m) 字符串匹配 ⭐⭐⭐⭐

🚀 高级算法专题

Dijkstra 最短路径算法

单源最短路径 | O((V+E)logV) | 不支持负权边

Python 使用堆优化
import heapq
from collections import defaultdict

def dijkstra(graph, start):
    """
    Dijkstra最短路径算法
    graph: {节点: [(邻居, 权重), ...]}
    """
    # 初始化距离
    distances = {node: float('inf') for node in graph}
    distances[start] = 0
    
    # 优先队列:(距离, 节点)
    pq = [(0, start)]
    visited = set()
    
    while pq:
        current_dist, current_node = heapq.heappop(pq)
        
        if current_node in visited:
            continue
        
        visited.add(current_node)
        
        # 更新邻居的距离
        for neighbor, weight in graph[current_node]:
            distance = current_dist + weight
            
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(pq, (distance, neighbor))
    
    return distances

# 使用示例
graph = {
    'A': [('B', 4), ('C', 2)],
    'B': [('C', 1), ('D', 5)],
    'C': [('D', 8), ('E', 10)],
    'D': [('E', 2)],
    'E': []
}

distances = dijkstra(graph, 'A')
print(distances)
# {'A': 0, 'B': 4, 'C': 2, 'D': 9, 'E': 11}

# ========== 带路径记录版本 ==========
def dijkstra_with_path(graph, start, end):
    """返回最短路径和距离"""
    distances = {node: float('inf') for node in graph}
    distances[start] = 0
    previous = {node: None for node in graph}
    
    pq = [(0, start)]
    visited = set()
    
    while pq:
        current_dist, current_node = heapq.heappop(pq)
        
        if current_node == end:
            break
        
        if current_node in visited:
            continue
        
        visited.add(current_node)
        
        for neighbor, weight in graph[current_node]:
            distance = current_dist + weight
            
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                previous[neighbor] = current_node
                heapq.heappush(pq, (distance, neighbor))
    
    # 重建路径
    path = []
    current = end
    while current is not None:
        path.append(current)
        current = previous[current]
    path.reverse()
    
    return distances[end], path

dist, path = dijkstra_with_path(graph, 'A', 'E')
print(f"最短距离: {dist}, 路径: {' -> '.join(path)}")
Go
package main

import (
    "container/heap"
    "fmt"
    "math"
)

type Edge struct {
    to     string
    weight int
}

type Item struct {
    node string
    dist int
    index int
}

type PriorityQueue []*Item

func (pq PriorityQueue) Len() int { return len(pq) }
func (pq PriorityQueue) Less(i, j int) bool { return pq[i].dist < pq[j].dist }
func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] }

func (pq *PriorityQueue) Push(x interface{}) {
    *pq = append(*pq, x.(*Item))
}

func (pq *PriorityQueue) Pop() interface{} {
    old := *pq
    n := len(old)
    item := old[n-1]
    *pq = old[0 : n-1]
    return item
}

func dijkstra(graph map[string][]Edge, start string) map[string]int {
    distances := make(map[string]int)
    for node := range graph {
        distances[node] = math.MaxInt32
    }
    distances[start] = 0
    
    pq := &PriorityQueue{}
    heap.Init(pq)
    heap.Push(pq, &Item{node: start, dist: 0})
    
    visited := make(map[string]bool)
    
    for pq.Len() > 0 {
        item := heap.Pop(pq).(*Item)
        node := item.node
        
        if visited[node] {
            continue
        }
        visited[node] = true
        
        for _, edge := range graph[node] {
            newDist := distances[node] + edge.weight
            if newDist < distances[edge.to] {
                distances[edge.to] = newDist
                heap.Push(pq, &Item{node: edge.to, dist: newDist})
            }
        }
    }
    
    return distances
}

func main() {
    graph := map[string][]Edge{
        "A": []Edge{{to: "B", weight: 4}, {to: "C", weight: 2}},
        "B": []Edge{{to: "D", weight: 5}},
        "C": []Edge{{to: "B", weight: 1}, {to: "D", weight: 8}},
        "D": {},
    }
    
    distances := dijkstra(graph, "A")
    fmt.Println(distances)
}

拓扑排序(Topological Sort)

有向无环图排序 | O(V+E) | 课程表问题

Python Kahn算法(BFS)
from collections import deque, defaultdict

def topological_sort(num_courses, prerequisites):
    """
    LeetCode 210: 课程表II
    输入: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
    输出: [0,1,2,3] 或 [0,2,1,3]
    """
    # 构建图和入度表
    graph = defaultdict(list)
    in_degree = [0] * num_courses
    
    for course, prereq in prerequisites:
        graph[prereq].append(course)
        in_degree[course] += 1
    
    # 将入度为0的课程加入队列
    queue = deque([i for i in range(num_courses) if in_degree[i] == 0])
    result = []
    
    while queue:
        course = queue.popleft()
        result.append(course)
        
        # 减少后续课程的入度
        for next_course in graph[course]:
            in_degree[next_course] -= 1
            if in_degree[next_course] == 0:
                queue.append(next_course)
    
    # 如果不能完成所有课程,说明有环
    return result if len(result) == num_courses else []

# 使用
prereqs = [[1,0],[2,0],[3,1],[3,2]]
order = topological_sort(4, prereqs)
print(f"课程顺序: {order}")

字典树(Trie / Prefix Tree)

前缀匹配 | O(m)插入/查询 | 自动补全

Python
class TrieNode:
    """Trie树节点"""
    def __init__(self):
        self.children = {}
        self.is_end = False  # 是否是单词结尾

class Trie:
    """字典树实现"""
    
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word):
        """插入单词"""
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.is_end = True
    
    def search(self, word):
        """搜索完整单词"""
        node = self.root
        for char in word:
            if char not in node.children:
                return False
            node = node.children[char]
        return node.is_end
    
    def starts_with(self, prefix):
        """搜索前缀"""
        node = self.root
        for char in prefix:
            if char not in node.children:
                return False
            node = node.children[char]
        return True
    
    def autocomplete(self, prefix):
        """自动补全:返回所有以prefix开头的单词"""
        node = self.root
        
        # 先找到前缀节点
        for char in prefix:
            if char not in node.children:
                return []
            node = node.children[char]
        
        # DFS收集所有单词
        results = []
        self._dfs(node, prefix, results)
        return results
    
    def _dfs(self, node, path, results):
        if node.is_end:
            results.append(path)
        
        for char, child in node.children.items():
            self._dfs(child, path + char, results)

# 使用示例
trie = Trie()
words = ["apple", "app", "application", "apply", "banana"]

for word in words:
    trie.insert(word)

print(trie.search("app"))  # True
print(trie.search("appl"))  # False
print(trie.starts_with("app"))  # True
print(trie.autocomplete("app"))  # ['app', 'apple', 'application', 'apply']
Go
package main

import "fmt"

type TrieNode struct {
    children map[rune]*TrieNode
    isEnd    bool
}

type Trie struct {
    root *TrieNode
}

func NewTrie() *Trie {
    return &Trie{
        root: &TrieNode{
            children: make(map[rune]*TrieNode),
        },
    }
}

func (t *Trie) Insert(word string) {
    node := t.root
    for _, ch := range word {
        if _, exists := node.children[ch]; !exists {
            node.children[ch] = &TrieNode{
                children: make(map[rune]*TrieNode),
            }
        }
        node = node.children[ch]
    }
    node.isEnd = true
}

func (t *Trie) Search(word string) bool {
    node := t.root
    for _, ch := range word {
        if _, exists := node.children[ch]; !exists {
            return false
        }
        node = node.children[ch]
    }
    return node.isEnd
}

func (t *Trie) StartsWith(prefix string) bool {
    node := t.root
    for _, ch := range prefix {
        if _, exists := node.children[ch]; !exists {
            return false
        }
        node = node.children[ch]
    }
    return true
}

func main() {
    trie := NewTrie()
    trie.Insert("apple")
    trie.Insert("app")
    
    fmt.Println(trie.Search("app"))      // true
    fmt.Println(trie.Search("appl"))     // false
    fmt.Println(trie.StartsWith("app"))  // true
}

Kruskal 最小生成树

贪心 + 并查集 | O(E log E) | 连接所有节点的最小代价

Python
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))
    
    def find(self, x):
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]
    
    def union(self, x, y):
        root_x, root_y = self.find(x), self.find(y)
        if root_x != root_y:
            self.parent[root_x] = root_y
            return True
        return False

def kruskal(n, edges):
    """
    Kruskal最小生成树
    n: 节点数
    edges: [[u, v, weight], ...]
    """
    # 按权重排序
    edges.sort(key=lambda x: x[2])
    
    uf = UnionFind(n)
    mst = []
    total_weight = 0
    
    for u, v, weight in edges:
        # 如果u和v不在同一集合,加入MST
        if uf.union(u, v):
            mst.append([u, v, weight])
            total_weight += weight
            
            # MST有n-1条边就完成了
            if len(mst) == n - 1:
                break
    
    return mst, total_weight

# 使用
n = 4
edges = [
    [0, 1, 10],
    [0, 2, 6],
    [0, 3, 5],
    [1, 3, 15],
    [2, 3, 4]
]

mst, weight = kruskal(n, edges)
print(f"最小生成树边: {mst}")
print(f"总权重: {weight}")  # 19

LRU缓存(Least Recently Used)

哈希表 + 双向链表 | O(1)读写 | 面试高频

Python
class Node:
    """双向链表节点"""
    def __init__(self, key=0, value=0):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None

class LRUCache:
    """LRU缓存实现"""
    
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.cache = {}  # key -> Node
        
        # 哨兵节点(简化边界处理)
        self.head = Node()
        self.tail = Node()
        self.head.next = self.tail
        self.tail.prev = self.head
    
    def _add_to_head(self, node):
        """将节点添加到头部(最近使用)"""
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node
    
    def _remove_node(self, node):
        """移除节点"""
        node.prev.next = node.next
        node.next.prev = node.prev
    
    def _move_to_head(self, node):
        """将节点移到头部"""
        self._remove_node(node)
        self._add_to_head(node)
    
    def _remove_tail(self):
        """移除尾部节点(最久未使用)"""
        node = self.tail.prev
        self._remove_node(node)
        return node
    
    def get(self, key: int) -> int:
        """获取值"""
        if key not in self.cache:
            return -1
        
        node = self.cache[key]
        self._move_to_head(node)
        return node.value
    
    def put(self, key: int, value: int) -> None:
        """插入或更新值"""
        if key in self.cache:
            # 更新已存在的key
            node = self.cache[key]
            node.value = value
            self._move_to_head(node)
        else:
            # 插入新key
            node = Node(key, value)
            self.cache[key] = node
            self._add_to_head(node)
            
            # 超出容量,移除最久未使用的
            if len(self.cache) > self.capacity:
                removed = self._remove_tail()
                del self.cache[removed.key]

# 使用示例
lru = LRUCache(2)
lru.put(1, 1)
lru.put(2, 2)
print(lru.get(1))    # 1
lru.put(3, 3)        # 移除key 2
print(lru.get(2))    # -1 (未找到)
lru.put(4, 4)        # 移除key 1
print(lru.get(1))    # -1
print(lru.get(3))    # 3
print(lru.get(4))    # 4

前缀和(Prefix Sum)

预处理优化区间查询 | O(1)查询 | 空间O(n)

Python
# ========== 一维前缀和 ==========
class NumArray:
    """区间和查询"""
    
    def __init__(self, nums):
        self.prefix = [0]
        for num in nums:
            self.prefix.append(self.prefix[-1] + num)
    
    def sum_range(self, left, right):
        """查询区间[left, right]的和"""
        return self.prefix[right + 1] - self.prefix[left]

# 使用
arr = NumArray([1, 3, 5, 7, 9])
print(arr.sum_range(0, 2))  # 9 (1+3+5)
print(arr.sum_range(1, 3))  # 15 (3+5+7)

# ========== 二维前缀和 ==========
class NumMatrix:
    """二维区间和查询"""
    
    def __init__(self, matrix):
        if not matrix or not matrix[0]:
            return
        
        m, n = len(matrix), len(matrix[0])
        self.prefix = [[0] * (n + 1) for _ in range(m + 1)]
        
        # 构建前缀和
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                self.prefix[i][j] = (
                    matrix[i-1][j-1] +
                    self.prefix[i-1][j] +
                    self.prefix[i][j-1] -
                    self.prefix[i-1][j-1]
                )
    
    def sum_region(self, row1, col1, row2, col2):
        """查询矩形区域的和"""
        return (
            self.prefix[row2+1][col2+1] -
            self.prefix[row1][col2+1] -
            self.prefix[row2+1][col1] +
            self.prefix[row1][col1]
        )

# 使用
matrix = [
    [3, 0, 1, 4, 2],
    [5, 6, 3, 2, 1],
    [1, 2, 0, 1, 5]
]
nm = NumMatrix(matrix)
print(nm.sum_region(1, 1, 2, 2))  # 11 (6+3+2+0)

单调栈(Monotonic Stack)

查找下一个更大/更小元素 | O(n) | 栈的巧妙应用

Python
def next_greater_element(nums):
    """
    找每个元素右边第一个比它大的元素
    输入: [2, 1, 2, 4, 3]
    输出: [4, 2, 4, -1, -1]
    """
    n = len(nums)
    result = [-1] * n
    stack = []  # 单调递减栈
    
    for i in range(n):
        # 当前元素比栈顶大,说明找到了栈顶元素的答案
        while stack and nums[i] > nums[stack[-1]]:
            idx = stack.pop()
            result[idx] = nums[i]
        
        stack.append(i)
    
    return result

print(next_greater_element([2, 1, 2, 4, 3]))

# ========== 每日温度问题 ==========
def daily_temperatures(temperatures):
    """
    LeetCode 739: 等多少天才会升温
    输入: [73,74,75,71,69,72,76,73]
    输出: [1,1,4,2,1,1,0,0]
    """
    n = len(temperatures)
    result = [0] * n
    stack = []
    
    for i in range(n):
        while stack and temperatures[i] > temperatures[stack[-1]]:
            idx = stack.pop()
            result[idx] = i - idx
        stack.append(i)
    
    return result

# ========== 柱状图中最大矩形 ==========
def largest_rectangle_area(heights):
    """
    LeetCode 84: 单调栈经典应用
    """
    stack = []
    max_area = 0
    heights = [0] + heights + [0]  # 哨兵
    
    for i, h in enumerate(heights):
        while stack and heights[stack[-1]] > h:
            height_idx = stack.pop()
            width = i - stack[-1] - 1
            area = heights[height_idx] * width
            max_area = max(max_area, area)
        
        stack.append(i)
    
    return max_area

print(largest_rectangle_area([2,1,5,6,2,3]))  # 10

📚 学习资源推荐

🎯 算法学习建议

📅

每日坚持

每天至少1题,保持算法思维活跃度。

🔄

重复刷题

同一题多次做,理解多种解法和优化思路。

📝

总结归纳

整理题型模板,建立自己的算法知识体系。