掌握核心算法,提升编程思维与问题解决能力
按照算法类型和应用场景分类,系统学习各类经典算法
将数据按特定顺序排列的基础算法。
在数据中查找目标的高效方法。
通过保存子问题解来优化递归的强大技术。
每步选择局部最优,追求全局最优解。
分而治之,将大问题分解为小问题求解。
通过试错和回退来寻找所有可能的解。
处理节点和边关系的专门算法。
处理文本和字符串的专用算法。
数论、组合数学相关的算法。
包含Python、Go、Java、C++四种语言的完整实现,时间复杂度分析,应用场景说明
分治思想的经典应用 | 平均O(n log n) | 不稳定排序
核心思想:选择一个基准元素(pivot),将数组分为两部分:小于基准的放左边,大于基准的放右边,然后递归处理两部分。
最好情况
O(n log n)
每次平分
平均情况
O(n log n)
随机基准
最坏情况
O(n²)
已排序
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]
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]
}
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
}
}
#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;
}
💡 优化技巧
对数时间复杂度 | O(log n) | 要求数组有序
每次比较中间元素,将搜索范围缩小一半,直到找到目标或范围为空。
def binary_search(arr, target):
"""二分搜索 - 迭代实现"""
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2 # 避免溢出
if arr[mid] == target:
return mid # 找到目标
elif arr[mid] < target:
left = mid + 1 # 在右半部分查找
else:
right = mid - 1 # 在左半部分查找
return -1 # 未找到
# 递归实现
def binary_search_recursive(arr, target, left, right):
if left > right:
return -1
mid = left + (right - left) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
return binary_search_recursive(arr, target, mid + 1, right)
else:
return binary_search_recursive(arr, target, left, mid - 1)
# 使用
arr = [1, 3, 5, 7, 9, 11, 13, 15]
index = binary_search(arr, 7)
print(f"找到位置: {index}") # 3
# ========== 二分搜索变种:查找边界 ==========
def search_left_bound(arr, target):
"""查找目标的最左边界"""
left, right = 0, len(arr)
while left < right:
mid = left + (right - left) // 2
if arr[mid] < target:
left = mid + 1
else:
right = mid
return left
def search_right_bound(arr, target):
"""查找目标的最右边界"""
left, right = 0, len(arr)
while left < right:
mid = left + (right - left) // 2
if arr[mid] <= target:
left = mid + 1
else:
right = mid
return left - 1
# 应用:查找区间
arr = [1, 2, 2, 2, 3, 4]
left = search_left_bound(arr, 2)
right = search_right_bound(arr, 2)
print(f"2出现在索引{left}到{right}") # 1到3
package main
import "fmt"
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
}
// 递归实现
func binarySearchRecursive(arr []int, target, left, right int) int {
if left > right {
return -1
}
mid := left + (right-left)/2
if arr[mid] == target {
return mid
} else if arr[mid] < target {
return binarySearchRecursive(arr, target, mid+1, right)
} else {
return binarySearchRecursive(arr, target, left, mid-1)
}
}
func main() {
arr := []int{1, 3, 5, 7, 9, 11, 13, 15}
index := binarySearch(arr, 7)
fmt.Printf("找到位置: %d\n", index)
}
public class BinarySearch {
public static 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;
}
public static void main(String[] args) {
int[] arr = {1, 3, 5, 7, 9, 11, 13, 15};
int index = binarySearch(arr, 7);
System.out.println("找到位置: " + index);
}
}
#include <iostream>
#include <vector>
#include <algorithm> // for std::binary_search
using namespace std;
int binarySearch(const vector<int>& arr, int target) {
int left = 0, right = arr.size() - 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;
}
int main() {
vector<int> arr = {1, 3, 5, 7, 9, 11, 13, 15};
// 自定义实现
int index = binarySearch(arr, 7);
cout << "找到位置: " << index << endl;
// STL实现
bool found = binary_search(arr.begin(), arr.end(), 7);
cout << "是否找到: " << (found ? "是" : "否") << endl;
// lower_bound和upper_bound
auto lower = lower_bound(arr.begin(), arr.end(), 7);
auto upper = upper_bound(arr.begin(), arr.end(), 7);
cout << "位置: " << (lower - arr.begin()) << endl;
return 0;
}
🎯 应用场景
图和树的核心遍历算法 | O(V+E) | V=顶点数 E=边数
核心思想:尽可能深地搜索分支,直到无路可走再回溯。类似"走迷宫沿着一条路走到底"。
# ========== 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 (层次遍历)
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应用场景
最优子结构 + 重叠子问题 | 空间换时间
核心思想:将复杂问题分解为子问题,保存子问题的解避免重复计算。
# ========== 示例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)
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
统计特定数字
穷举所有可能 + 剪枝优化 | 指数时间复杂度
# ========== 示例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(选择)
处理连续子数组 | O(n) | 双指针变种
# ========== 无重复字符的最长子串 ==========
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"
一次遍历解决问题 | O(n) | 空间O(1)
# ========== 两数之和(有序数组) ==========
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
处理集合合并与查询 | 接近O(1) | 路径压缩
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
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)
}
局部最优 → 全局最优 | 需要证明贪心性质
# ========== 区间调度问题 ==========
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步到末尾)
高效字符串匹配 | O(m+n) | 部分匹配表
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
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) | 字符串匹配 | ⭐⭐⭐⭐ |
单源最短路径 | O((V+E)logV) | 不支持负权边
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)}")
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)
}
有向无环图排序 | O(V+E) | 课程表问题
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}")
前缀匹配 | O(m)插入/查询 | 自动补全
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']
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
}
贪心 + 并查集 | O(E log E) | 连接所有节点的最小代价
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
哈希表 + 双向链表 | O(1)读写 | 面试高频
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
预处理优化区间查询 | O(1)查询 | 空间O(n)
# ========== 一维前缀和 ==========
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)
查找下一个更大/更小元素 | O(n) | 栈的巧妙应用
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题,保持算法思维活跃度。
同一题多次做,理解多种解法和优化思路。
整理题型模板,建立自己的算法知识体系。