数据结构与算法
难度等级:⭐⭐⭐⭐ 前置知识:编程语言基础 后续衔接:架构设计、性能优化
学习路径
- 入门阶段:掌握基础数据结构(数组、链表、栈、队列)和简单算法(排序、查找)
- 进阶阶段:理解树、图、哈希表等复杂数据结构,掌握递归、分治、贪心思想
- 精通阶段:熟练运用动态规划、图算法解决实际问题,能够进行算法复杂度分析和优化
一、复杂度分析
1.1 时间复杂度
概念解释
时间复杂度是衡量算法执行时间随输入规模增长的变化趋势,使用大 O 记号表示。它忽略常数项和低阶项,只关注最高阶项,反映算法的渐近性能。
核心要点
- 常见复杂度(从优到劣):O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ) < O(n!)
- 分析原则:关注循环嵌套层数、递归深度、分支因子
- 最坏情况、平均情况、最好情况:通常关注最坏情况,保证性能下限
- 均摊复杂度:某些操作偶尔耗时长,但平均下来复杂度较低(如动态数组扩容)
代码示例
# O(1) - 常数时间
def get_first(arr):
return arr[0]
# O(log n) - 对数时间(二分查找)
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
# O(n) - 线性时间
def find_max(arr):
max_val = arr[0]
for num in arr[1:]:
if num > max_val:
max_val = num
return max_val
# O(n log n) - 线性对数时间(归并排序)
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
# O(n²) - 平方时间(冒泡排序)
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
实际应用场景
- 算法选型:根据数据规模选择合适算法(小数据用 O(n²),大数据用 O(n log n))
- 性能瓶颈定位:通过复杂度分析找到代码中的性能瓶颈
- 面试必备:几乎所有技术面试都会考察复杂度分析能力
1.2 空间复杂度
概念解释
空间复杂度是衡量算法执行过程中所需额外内存空间随输入规模增长的变化趋势。不包括输入数据本身占用的空间,只计算算法运行过程中临时分配的额外空间。
核心要点
- 原地算法(In-place):空间复杂度 O(1),如快速排序、堆排序
- 递归算法:需要考虑调用栈深度(如快速排序 O(log n),归并排序 O(n))
- 空间换时间:使用额外空间降低时间复杂度(如哈希表、缓存、备忘录)
- 常见空间复杂度:O(1) < O(log n) < O(n) < O(n²)
代码示例
# O(1) 空间 - 双指针反转数组
def reverse_array(arr):
left, right = 0, len(arr) - 1
while left < right:
arr[left], arr[right] = arr[right], arr[left]
left += 1
right -= 1
# O(n) 空间 - 归并排序需要额外数组
def merge(left, right):
result = [] # 额外空间 O(n)
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
# O(n) 空间 - 动态规划备忘录
def fibonacci_memo(n, memo={}):
if n in memo:
return memo[n]
if n <= 2:
return 1
memo[n] = fibonacci_memo(n-1, memo) + fibonacci_memo(n-2, memo)
return memo[n]
二、基础数据结构
2.1 数组(Array)
概念解释
数组是最基础的线性数据结构,在内存中连续存储相同类型的元素。支持通过索引 O(1) 时间随机访问,但插入和删除操作需要移动元素,时间复杂度为 O(n)。
核心要点
- 连续内存分配,支持 O(1) 随机访问
- 固定大小(静态数组)或动态扩容(动态数组,如 Python list、Java ArrayList)
- 动态数组扩容策略:通常扩容 2 倍或 1.5 倍,均摊复杂度 O(1)
- 缓存友好:连续内存利用 CPU 缓存行,访问速度快
代码示例
# Python 动态数组
arr = [1, 2, 3, 4, 5]
arr.append(6) # O(1) 均摊
arr.insert(0, 0) # O(n)
arr.pop() # O(1)
arr.pop(0) # O(n)
arr[2] # O(1) 随机访问
# 双指针技巧 - 两数之和(有序数组)
def two_sum_sorted(arr, target):
left, right = 0, len(arr) - 1
while left < right:
current_sum = arr[left] + arr[right]
if current_sum == target:
return [left, right]
elif current_sum < target:
left += 1
else:
right -= 1
return [-1, -1]
关联知识点:动态数组实现、二分查找、双指针技巧、滑动窗口
2.2 链表(Linked List)
概念解释
链表是一种非连续存储的线性数据结构,由一系列节点组成,每个节点包含数据域和指针域。链表通过指针将分散的节点连接起来,插入和删除操作无需移动元素,时间复杂度为 O(1)(已知位置时)。
核心要点
- 单链表:每个节点只有一个 next 指针
- 双链表:每个节点有 prev 和 next 两个指针,支持双向遍历
- 循环链表:尾节点指向头节点,形成环
- 虚拟头节点(Dummy Head):简化边界条件处理
- 快慢指针:检测环、找中点、倒数第 k 个节点
代码示例
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
# 反转链表
def reverse_list(head):
prev = None
current = head
while current:
next_node = current.next
current.next = prev
prev = current
current = next_node
return prev
# 检测链表是否有环(快慢指针)
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
# 找到链表中间节点
def find_middle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
# 合并两个有序链表
def merge_two_lists(l1, l2):
dummy = ListNode(0)
current = dummy
while l1 and l2:
if l1.val < l2.val:
current.next = l1
l1 = l1.next
else:
current.next = l2
l2 = l2.next
current = current.next
current.next = l1 or l2
return dummy.next
实际应用场景
- LRU 缓存淘汰算法(哈希表 + 双链表)
- 浏览器前进后退功能(双链表)
- 多项式加法(链表存储稀疏多项式)
- 图的邻接表表示
关联知识点:双指针技巧、递归、哈希表、LRU 缓存
2.3 栈(Stack)
概念解释
栈是后进先出(LIFO, Last In First Out)的线性数据结构,只允许在栈顶进行插入(push)和删除(pop)操作。栈在函数调用、表达式求值、括号匹配等场景中有广泛应用。
核心要点
- 基本操作:push(入栈)、pop(出栈)、peek/top(查看栈顶)、isEmpty(判空)
- 数组实现:简单高效,但需要处理扩容问题
- 链表实现:动态大小,无需扩容
- 单调栈:维护栈内元素单调性,用于找下一个更大/更小元素
代码示例
# Python 使用 list 作为栈
stack = []
stack.append(1) # push
stack.append(2)
stack.pop() # pop -> 2
stack[-1] # peek -> 1
# 有效的括号(LeetCode 20)
def is_valid(s):
stack = []
mapping = {')': '(', '}': '{', ']': '['}
for char in s:
if char in mapping:
top_element = stack.pop() if stack else '#'
if mapping[char] != top_element:
return False
else:
stack.append(char)
return not stack
# 单调栈 - 每日温度(LeetCode 739)
def daily_temperatures(temperatures):
result = [0] * len(temperatures)
stack = [] # 存储索引
for i, temp in enumerate(temperatures):
while stack and temperatures[stack[-1]] < temp:
prev_index = stack.pop()
result[prev_index] = i - prev_index
stack.append(i)
return result
实际应用场景
- 函数调用栈:记录函数调用关系和局部变量
- 表达式求值:中缀表达式转后缀表达式
- 浏览器后退功能:历史记录栈
- DFS 深度优先搜索:隐式使用调用栈
关联知识点:递归、表达式树、DFS
2.4 队列(Queue)
概念解释
队列是先进先出(FIFO, First In First Out)的线性数据结构,允许在队尾插入(enqueue),在队首删除(dequeue)。队列在 BFS、任务调度、消息传递等场景中发挥重要作用。
核心要点
- 基本操作:enqueue(入队)、dequeue(出队)、front(查看队首)、isEmpty(判空)
- 循环队列:利用数组首尾相连,提高空间利用率
- 双端队列(Deque):支持两端插入和删除
- 优先队列:基于堆实现,按优先级出队
代码示例
from collections import deque
# Python 双端队列
queue = deque()
queue.append(1) # 队尾入队
queue.append(2)
queue.popleft() # 队首出队 -> 1
queue[0] # 查看队首
# BFS 广度优先搜索(二叉树层序遍历)
def level_order(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return result
# 滑动窗口最大值(单调队列)
def max_sliding_window(nums, k):
result = []
deque_indices = deque()
for i, num in enumerate(nums):
if deque_indices and deque_indices[0] == i - k:
deque_indices.popleft()
while deque_indices and nums[deque_indices[-1]] < num:
deque_indices.pop()
deque_indices.append(i)
if i >= k - 1:
result.append(nums[deque_indices[0]])
return result
实际应用场景
- BFS 广度优先搜索:最短路径、层序遍历
- 任务调度:操作系统进程调度、线程池
- 消息队列:异步处理、解耦系统
- 滑动窗口算法:固定/可变窗口问题
关联知识点:BFS、堆、滑动窗口、单调队列
2.5 哈希表(Hash Table)
概念解释
哈希表是基于哈希函数实现的高效查找数据结构,通过键值对(Key-Value)存储数据。理想情况下,插入、删除、查找操作的时间复杂度均为 O(1)。
核心要点
- 哈希函数:将任意长度的键映射为固定范围的整数
- 冲突解决:链地址法(拉链法)、开放地址法(线性探测、二次探测)
- 负载因子(Load Factor):元素数量/桶数量,过大时扩容
- Python dict、Java HashMap、C++ unordered_map 均基于哈希表实现
代码示例
# Python 字典
hash_map = {}
hash_map['key1'] = 'value1' # 插入 O(1)
hash_map['key2'] = 'value2'
value = hash_map.get('key1') # 查找 O(1)
del hash_map['key1'] # 删除 O(1)
# 两数之和(LeetCode 1)
def two_sum(nums, target):
hash_map = {}
for i, num in enumerate(nums):
complement = target - num
if complement in hash_map:
return [hash_map[complement], i]
hash_map[num] = i
return []
# 字母异位词分组(LeetCode 49)
def group_anagrams(strs):
groups = {}
for s in strs:
key = tuple(sorted(s))
if key not in groups:
groups[key] = []
groups[key].append(s)
return list(groups.values())
# 最长连续序列(LeetCode 128)
def longest_consecutive(nums):
num_set = set(nums)
longest = 0
for num in num_set:
if num - 1 not in num_set:
current_num = num
current_length = 1
while current_num + 1 in num_set:
current_num += 1
current_length += 1
longest = max(longest, current_length)
return longest
实际应用场景
- 缓存系统:Redis、Memcached
- 数据库索引:哈希索引
- 去重:Set 数据结构
- 词频统计:MapReduce 中的 WordCount
关联知识点:布隆过滤器、一致性哈希、LRU 缓存
三、树结构
3.1 二叉树(Binary Tree)
概念解释
二叉树是每个节点最多有两个子节点(左子节点和右子节点)的树形数据结构。二叉树是树结构的基础,衍生出二叉搜索树、平衡二叉树、堆等多种变体。
核心要点
- 遍历方式:前序(根左右)、中序(左根右)、后序(左右根)、层序(BFS)
- 递归遍历:代码简洁,但递归深度过大可能导致栈溢出
- 迭代遍历:使用栈模拟递归,避免栈溢出
- Morris 遍历:O(1) 空间复杂度,利用线索二叉树思想
代码示例
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# 前序遍历(递归)
def preorder_recursive(root):
if not root:
return []
return [root.val] + preorder_recursive(root.left) + preorder_recursive(root.right)
# 中序遍历(迭代)
def inorder_iterative(root):
result = []
stack = []
current = root
while stack or current:
while current:
stack.append(current)
current = current.left
current = stack.pop()
result.append(current.val)
current = current.right
return result
# 层序遍历(BFS)
def level_order(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return result
# 二叉树最大深度
def max_depth(root):
if not root:
return 0
return 1 + max(max_depth(root.left), max_depth(root.right))
# 验证二叉搜索树(中序遍历递增)
def is_valid_bst(root):
stack = []
current = root
prev = None
while stack or current:
while current:
stack.append(current)
current = current.left
current = stack.pop()
if prev and current.val <= prev.val:
return False
prev = current
current = current.right
return True
关联知识点:递归、DFS、BFS、二叉搜索树、平衡二叉树
3.2 二叉搜索树(BST)
概念解释
二叉搜索树是一种特殊的二叉树,满足以下性质:左子树所有节点的值 < 根节点的值 < 右子树所有节点的值,且左右子树也分别是二叉搜索树。BST 支持高效的查找、插入、删除操作。
核心要点
- 查找、插入、删除平均时间复杂度:O(log n),最坏情况(退化为链表):O(n)
- 中序遍历 BST 得到递增序列
- 查找操作:从根节点开始,根据大小关系决定向左或向右子树查找
- 删除操作分三种情况:叶子节点、单子节点、双子节点(找后继或前驱替换)
代码示例
# BST 查找
def search_bst(root, val):
if not root or root.val == val:
return root
if val < root.val:
return search_bst(root.left, val)
return search_bst(root.right, val)
# BST 插入
def insert_bst(root, val):
if not root:
return TreeNode(val)
if val < root.val:
root.left = insert_bst(root.left, val)
else:
root.right = insert_bst(root.right, val)
return root
# BST 删除
def delete_bst(root, key):
if not root:
return None
if key < root.val:
root.left = delete_bst(root.left, key)
elif key > root.val:
root.right = delete_bst(root.right, key)
else:
if not root.left:
return root.right
if not root.right:
return root.left
min_node = get_min(root.right)
root.val = min_node.val
root.right = delete_bst(root.right, min_node.val)
return root
def get_min(node):
while node.left:
node = node.left
return node
关联知识点:AVL 树、红黑树、B/B+ 树、数据库索引
四、图结构
4.1 图的基本表示
概念解释
图由顶点(Vertex)和边(Edge)组成,用于表示多对多的关系。根据边是否有方向,分为有向图和无向图;根据边是否有权重,分为带权图和无权图。
核心要点
- 邻接矩阵:二维数组
graph[i][j]表示顶点 i 到 j 的边,适合稠密图,查询 O(1),空间 O(V²) - 邻接表:每个顶点维护一个链表/列表,适合稀疏图,空间 O(V+E)
- 邻接矩阵 vs 邻接表选择:顶点数多、边少用邻接表;需要快速判断两点是否相邻用邻接矩阵
代码示例
# 邻接矩阵表示
class GraphMatrix:
def __init__(self, vertices):
self.V = vertices
self.graph = [[0] * vertices for _ in range(vertices)]
def add_edge(self, u, v, weight=1):
self.graph[u][v] = weight
# 无向图:self.graph[v][u] = weight
# 邻接表表示
from collections import defaultdict
class GraphList:
def __init__(self):
self.graph = defaultdict(list)
def add_edge(self, u, v, weight=1):
self.graph[u].append((v, weight))
# 无向图:self.graph[v].append((u, weight))
关联知识点:社交网络、地图导航、依赖分析
4.2 图的遍历(BFS 与 DFS)
概念解释
- BFS(广度优先搜索):从起点出发,逐层向外扩展,使用队列实现,适合求最短路径(无权图)
- DFS(深度优先搜索):从起点出发,沿一条路径走到底再回溯,使用栈(或递归)实现,适合路径搜索、连通性判断
核心要点
- BFS 使用队列,DFS 使用栈(递归本质也是栈)
- BFS 时间 O(V+E),空间 O(V);DFS 时间 O(V+E),空间 O(h)(h 为递归深度)
- BFS 可求无权图最短路径,DFS 可检测环、拓扑排序
代码示例
from collections import deque
def bfs(graph, start):
visited = set([start])
queue = deque([start])
result = []
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return result
def dfs(graph, start, visited=None):
if visited is None:
visited = set()
visited.add(start)
result = [start]
for neighbor in graph[start]:
if neighbor not in visited:
result.extend(dfs(graph, neighbor, visited))
return result
关联知识点:迷宫求解、层级遍历、连通分量
4.3 最短路径算法
概念解释
最短路径算法用于求图中两个顶点之间权值和最小的路径。经典算法包括 Dijkstra(单源最短路径,无负权边)和 Floyd-Warshall(多源最短路径)。
核心要点
- Dijkstra:贪心策略,每次选距起点最近的未访问顶点,时间 O((V+E)logV)(优先队列)
- Bellman-Ford:可处理负权边,时间 O(VE),可检测负权环
- Floyd-Warshall:动态规划,求所有顶点对的最短路径,时间 O(V³)
代码示例
import heapq
def dijkstra(graph, start):
# graph: {u: [(v, weight), ...]}
dist = {node: float('inf') for node in graph}
dist[start] = 0
heap = [(0, start)]
while heap:
d, u = heapq.heappop(heap)
if d > dist[u]:
continue
for v, w in graph[u]:
if dist[u] + w < dist[v]:
dist[v] = dist[u] + w
heapq.heappush(heap, (dist[v], v))
return dist
def floyd_warshall(graph, n):
# graph: 邻接矩阵, n: 顶点数
dist = [row[:] for row in graph]
for k in range(n):
for i in range(n):
for j in range(n):
if dist[i][k] + dist[k][j] < dist[i][j]:
dist[i][j] = dist[i][k] + dist[k][j]
return dist
关联知识点:导航路线规划、网络路由、物流配送
4.4 最小生成树
概念解释
最小生成树(MST)是连通无向图中权值之和最小的生成树。包含图中所有顶点且边数最少(V-1 条),主要用于网络设计等场景。
核心要点
- Kruskal:按边权排序,依次选最小边(不形成环),使用并查集判断,时间 O(ElogE)
- Prim:从任一顶点出发,每次选连接已访问与未访问顶点的最小边,时间 O(ElogV)
- 并查集路径压缩 + 按秩合并:近似 O(1) 查询
代码示例
# Kruskal 算法
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * 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):
px, py = self.find(x), self.find(y)
if px == py:
return False
if self.rank[px] < self.rank[py]:
px, py = py, px
self.parent[py] = px
if self.rank[px] == self.rank[py]:
self.rank[px] += 1
return True
def kruskal(edges, n):
# edges: [(weight, u, v), ...]
edges.sort()
uf = UnionFind(n)
mst = []
total = 0
for w, u, v in edges:
if uf.union(u, v):
mst.append((u, v, w))
total += w
if len(mst) == n - 1:
break
return mst, total
关联知识点:网络布线、管道铺设、聚类分析
4.5 拓扑排序
概念解释
拓扑排序是对有向无环图(DAG)的顶点的一种线性排序,使得对于每条有向边 (u, v),u 都排在 v 之前。常用于任务调度、编译依赖等场景。
核心要点
- Kahn 算法(BFS):不断删除入度为 0 的顶点,时间 O(V+E)
- DFS 后序反转:DFS 完成时逆序输出,时间 O(V+E)
- 若排序后顶点数 < 总顶点数,说明图中有环
代码示例
from collections import deque, defaultdict
def topological_sort_kahn(graph, n):
# graph: {u: [v1, v2, ...]}
in_degree = [0] * n
for u in graph:
for v in graph[u]:
in_degree[v] += 1
queue = deque([i for i in range(n) if in_degree[i] == 0])
result = []
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph.get(node, []):
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
if len(result) != n:
return None # 有环
return result
关联知识点:编译顺序、课程规划、构建系统依赖
五、排序算法
5.1 基础排序(O(n²))
概念解释
基础排序算法时间复杂度为 O(n²),实现简单,适合小规模数据或作为教学入门。包括冒泡排序、选择排序和插入排序。
核心要点
- 冒泡排序:相邻元素两两比较,每轮将最大元素”冒泡”到末尾,稳定
- 选择排序:每轮选最小元素放到已排序末尾,不稳定
- 插入排序:将未排序元素逐个插入已排序序列的正确位置,稳定,近乎有序时接近 O(n)
代码示例
def bubble_sort(arr):
n = len(arr)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped: # 优化:无交换则已有序
break
return arr
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
关联知识点:小数据排序、Nearly sorted 优化、TimSort 基础
5.2 高级排序(O(n log n))
概念解释
高级排序算法通过分治策略将时间复杂度优化到 O(n log n),包括快速排序、归并排序和堆排序。
核心要点
- 快速排序:选基准(pivot),小于基准放左、大于放右,递归排序左右子数组。平均 O(n log n),最坏 O(n²),原地排序,不稳定
- 归并排序:分治→排序→合并,稳定 O(n log n),需额外 O(n) 空间
- 堆排序:建大顶堆→交换堆顶与末尾→调整堆,原地 O(n log n),不稳定
代码示例
def quick_sort(arr, low=0, high=None):
if high is None:
high = len(arr) - 1
if low < high:
pivot_idx = partition(arr, low, high)
quick_sort(arr, low, pivot_idx - 1)
quick_sort(arr, pivot_idx + 1, high)
return arr
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
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
return merge(left, right)
def merge(left, right):
result, i, j = [], 0, 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i]); i += 1
else:
result.append(right[j]); j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
def heap_sort(arr):
import heapq
heapq.heapify(arr) # 建小顶堆 O(n)
return [heapq.heappop(arr) for _ in range(len(arr))]
关联知识点:数据库排序、大数据外部排序、Java/DualPivotQuicksort
5.3 非比较排序
概念解释
非比较排序不通过元素间的比较来确定顺序,而是利用元素的特性(如范围、位数),可突破 O(n log n) 下界,达到 O(n) 线性时间。
核心要点
- 计数排序:统计每个值出现次数,适合值范围不大的整数排序,O(n+k)
- 桶排序:将元素分到有限数量的桶中,每个桶内再排序,O(n+k)
- 基数排序:按位排序(个位→十位→百位),O(d·(n+k)),适合数字/字符串排序
代码示例
def counting_sort(arr):
if not arr:
return arr
min_val, max_val = min(arr), max(arr)
count = [0] * (max_val - min_val + 1)
for num in arr:
count[num - min_val] += 1
result = []
for i, c in enumerate(count):
result.extend([i + min_val] * c)
return result
def radix_sort(arr):
if not arr:
return arr
max_val = max(arr)
exp = 1
while max_val // exp > 0:
arr = counting_sort_by_digit(arr, exp)
exp *= 10
return arr
def counting_sort_by_digit(arr, exp):
n = len(arr)
output = [0] * n
count = [0] * 10
for num in arr:
count[(num // exp) % 10] += 1
for i in range(1, 10):
count[i] += count[i - 1]
for i in range(n - 1, -1, -1):
digit = (arr[i] // exp) % 10
output[count[digit] - 1] = arr[i]
count[digit] -= 1
return output
关联知识点:字符串排序、稳定排序、大数据处理
六、查找算法
6.1 二分查找
概念解释
二分查找是在有序数组中通过不断将搜索区间减半来定位目标值的算法。前提是数据必须有序,时间复杂度 O(log n),是最高效的查找方式之一。
核心要点
- 基本二分:
left <= right,mid = left + (right - left) // 2 - 左边界二分:找第一个等于 target 的位置,
right = mid - 1后记录结果 - 右边界二分:找最后一个等于 target 的位置,
left = mid + 1后记录结果 - 旋转排序数组:先判断左半还是右半有序,再决定搜索方向
代码示例
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 search_rotated(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
return mid
if arr[left] <= arr[mid]: # 左半有序
if arr[left] <= target < arr[mid]:
right = mid - 1
else:
left = mid + 1
else: # 右半有序
if arr[mid] < target <= arr[right]:
left = mid + 1
else:
right = mid - 1
return -1
关联知识点:数据库索引查找、区间搜索、求平方根
6.2 哈希查找
概念解释
哈希查找通过哈希函数将键映射到数组下标,实现近 O(1) 的查找。核心在于哈希函数设计和冲突处理。
核心要点
- 哈希函数:均匀分布、计算快,常用取模法
key % size - 冲突解决:链地址法(每个桶挂链表/树)、开放地址法(线性探测、二次探测、双重哈希)
- 装载因子:
元素数/桶数,超过阈值(Java HashMap 0.75)需要扩容 - Java HashMap:数组 + 链表 → 链表长度 > 8 转红黑树
代码示例
class HashMap:
def __init__(self, size=16):
self.size = size
self.buckets = [[] for _ in range(size)]
def _hash(self, key):
return hash(key) % self.size
def put(self, key, value):
bucket = self.buckets[self._hash(key)]
for i, (k, v) in enumerate(bucket):
if k == key:
bucket[i] = (key, value)
return
bucket.append((key, value))
def get(self, key):
bucket = self.buckets[self._hash(key)]
for k, v in bucket:
if k == key:
return v
return None
def remove(self, key):
bucket = self.buckets[self._hash(key)]
for i, (k, v) in enumerate(bucket):
if k == key:
bucket.pop(i)
return
关联知识点:HashMap 源码、LRU 缓存、布隆过滤器
七、算法思想
7.1 递归与分治
概念解释
- 递归:函数调用自身,将问题分解为更小的同类子问题。必须有递归基(终止条件)和递归体
- 分治:将问题分解为多个独立子问题,分别求解后合并。典型应用:归并排序、快速排序
核心要点
- 递归三要素:参数、终止条件、递归调用
- 分治三步骤:分解(Divide)→ 解决(Conquer)→ 合并(Combine)
- 递归优化:尾递归优化、记忆化(缓存已计算结果)
- 主定理:T(n) = aT(n/b) + O(n^d),根据 a、b、d 关系判断复杂度
代码示例
# 归并排序(分治经典)
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid]) # 分解
right = merge_sort(arr[mid:]) # 分解
return merge(left, right) # 合并
# 快速幂(分治优化)
def fast_pow(x, n):
if n == 0:
return 1
half = fast_pow(x, n // 2)
if n % 2 == 0:
return half * half
return half * half * x
关联知识点:树的遍历、汉诺塔、Strassen 矩阵乘法
7.2 贪心算法
概念解释
贪心算法在每一步都选择当前看起来最优的选项,期望通过局部最优达到全局最优。贪心不一定能得到最优解,但很多经典问题可以。
核心要点
- 贪心选择性质:局部最优选择可以导致全局最优
- 最优子结构:问题的最优解包含子问题的最优解
- 经典贪心问题:活动选择、Huffman 编码、Dijkstra、Prim
- 证明贪心正确性:交换论证法、归纳法
代码示例
# 活动选择问题
def activity_selection(activities):
# activities: [(start, end), ...]
activities.sort(key=lambda x: x[1]) # 按结束时间排序
selected = [activities[0]]
last_end = activities[0][1]
for start, end in activities[1:]:
if start >= last_end:
selected.append((start, end))
last_end = end
return selected
# 零钱兑换(贪心,不保证最优,仅适用于特定面额)
def coin_change_greedy(amount, coins):
coins.sort(reverse=True)
count = 0
for coin in coins:
if amount >= coin:
count += amount // coin
amount %= coin
return count if amount == 0 else -1
关联知识点:区间调度、最小生成树、霍夫曼压缩
7.3 回溯算法
概念解释
回溯算法通过试探性地构造解,当发现当前路径不可能得到有效解时,回退到上一步继续尝试其他选择。本质是带剪枝的深度优先搜索。
核心要点
- 回溯框架:选择 → 递归 → 撤销选择
- 剪枝策略:提前判断当前路径不可能产生有效解,跳过
- 经典问题:N 皇后、全排列、子集、组合总和
- 时间复杂度通常是指数级 O(2^n) 或 O(n!)
代码示例
# 全排列
def permute(nums):
result = []
def backtrack(path, remaining):
if not remaining:
result.append(path[:])
return
for i, num in enumerate(remaining):
path.append(num) # 选择
backtrack(path, remaining[:i] + remaining[i+1:]) # 递归
path.pop() # 撤销
backtrack([], nums)
return result
# N 皇后
def solve_n_queens(n):
result = []
def backtrack(row, cols, diag1, diag2, board):
if row == n:
result.append([''.join(r) for r in board])
return
for col in range(n):
if col in cols or row - col in diag1 or row + col in diag2:
continue # 剪枝
board[row][col] = 'Q'
backtrack(row + 1, cols | {col}, diag1 | {row - col}, diag2 | {row + col}, board)
board[row][col] = '.'
backtrack(0, set(), set(), set(), [['.'] * n for _ in range(n)])
return result
关联知识点:约束满足问题、数独求解、正则表达式引擎
八、动态规划
8.1 动态规划基础
概念解释
动态规划(Dynamic Programming, DP)将复杂问题分解为重叠子问题,通过存储子问题的解避免重复计算。核心是最优子结构和状态转移方程。
核心要点
- 最优子结构:问题的最优解包含子问题的最优解
- 状态转移方程:定义 dp[i] 的含义,写出 dp[i] 与 dp[i-1]…的关系
- 重叠子问题:子问题会被重复计算,记忆化/打表避免
- 两种实现:自顶向下(记忆化递归)、自底向上(迭代打表)
- 解题步骤:定义状态 → 写转移方程 → 确定初始值 → 确定遍历顺序
代码示例
# 斐波那契数列
# 递归(指数级)→ 记忆化 → 迭代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
prev2, prev1 = 0, 1
for i in range(2, n + 1):
curr = prev1 + prev2
prev2, prev1 = prev1, curr
return prev1
关联知识点:记忆化搜索、滚动数组优化、状态压缩
8.2 背包问题
概念解释
背包问题是 DP 经典题型:在容量有限的背包中,选择物品使总价值最大。根据物品是否可分割、是否可重复选取,分为 0-1 背包、完全背包、分数背包。
核心要点
- 0-1 背包:每个物品选或不选,
dp[i][w] = max(dp[i-1][w], dp[i-1][w-wi] + vi) - 完全背包:物品可重复选,
dp[i][w] = max(dp[i-1][w], dp[i][w-wi] + vi) - 一维优化:0-1 背包逆序遍历,完全背包正序遍历
- 分数背包:贪心即可(单位价值排序)
代码示例
# 0-1 背包
def knapsack_01(weights, values, capacity):
n = len(weights)
dp = [0] * (capacity + 1)
for i in range(n):
for w in range(capacity, weights[i] - 1, -1): # 逆序
dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
return dp[capacity]
# 完全背包
def knapsack_complete(weights, values, capacity):
n = len(weights)
dp = [0] * (capacity + 1)
for i in range(n):
for w in range(weights[i], capacity + 1): # 正序
dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
return dp[capacity]
关联知识点:零钱兑换、组合总和、资源分配
8.3 最长子序列问题
概念解释
最长子序列问题是 DP 高频考点,包括最长递增子序列(LIS)、最长公共子序列(LCS)、最长公共子串等。
核心要点
- LIS:
dp[i]= 以 nums[i] 结尾的 LIS 长度,O(n²);二分优化 O(n log n) - LCS:
dp[i][j]= text1 前 i 个与 text2 前 j 个的 LCS 长度 - 最长公共子串:要求连续,
dp[i][j]仅当text1[i-1]==text2[j-1]时dp[i][j]=dp[i-1][j-1]+1 - 编辑距离:
dp[i][j]= word1 前 i 个变 word2 前 j 个的最少操作数
代码示例
# 最长递增子序列(LIS)- O(n log n) 解法
def length_of_lis(nums):
tails = [] # tails[i] = 长度为 i+1 的 LIS 的最小末尾
for num in nums:
left, right = 0, len(tails)
while left < right:
mid = (left + right) // 2
if tails[mid] < num:
left = mid + 1
else:
right = mid
if left == len(tails):
tails.append(num)
else:
tails[left] = num
return len(tails)
# 最长公共子序列(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]
# 编辑距离
def min_distance(word1, word2):
m, n = len(word1), len(word2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1): dp[i][0] = i
for j in range(n + 1): dp[0][j] = j
for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])
return dp[m][n]
关联知识点:DNA 序列比对、文本 diff、拼写纠错
8.4 常见 DP 模型
概念解释
掌握常见 DP 模型可以快速识别并解题。高频模型包括:路径 DP、区间 DP、状态机 DP、数位 DP 等。
核心要点
- 路径 DP:网格从左上到右下的路径数/最小路径和,
dp[i][j]由上方和左方转移 - 区间 DP:
dp[i][j]表示区间 [i,j] 的最优解,通常需枚举分割点 k,O(n³) - 状态机 DP:有限状态(如”持有/不持有股票”),
dp[i][state]按状态转移 - 数位 DP:统计满足条件的数字个数,记忆化搜索实现
代码示例
# 最小路径和
def min_path_sum(grid):
m, n = len(grid), len(grid[0])
dp = [[0] * n for _ in range(m)]
dp[0][0] = grid[0][0]
for i in range(1, m): dp[i][0] = dp[i-1][0] + grid[i][0]
for j in range(1, n): dp[0][j] = dp[0][j-1] + grid[0][j]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
return dp[m-1][n-1]
# 买卖股票(状态机 DP - 最多 k 次交易)
def max_profit_k(k, prices):
n = len(prices)
if n <= 1 or k == 0:
return 0
if k >= n // 2: # 退化为无限次交易
profit = 0
for i in range(1, n):
profit += max(0, prices[i] - prices[i-1])
return profit
# dp[i][k][0/1] = 第i天、第k次交易、不持有/持有
dp = [[[0, float('-inf')] for _ in range(k + 1)] for _ in range(n)]
for i in range(n):
for j in range(1, k + 1):
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i])
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i])
return dp[n-1][k][0]
关联知识点:LeetCode Hot 100、面试高频题型、打家劫舍系列