数据结构与算法

难度等级:⭐⭐⭐⭐ 前置知识:编程语言基础 后续衔接:架构设计、性能优化

学习路径


一、复杂度分析

1.1 时间复杂度

概念解释

时间复杂度是衡量算法执行时间随输入规模增长的变化趋势,使用大 O 记号表示。它忽略常数项和低阶项,只关注最高阶项,反映算法的渐近性能。

核心要点

代码示例

# 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]

实际应用场景

1.2 空间复杂度

概念解释

空间复杂度是衡量算法执行过程中所需额外内存空间随输入规模增长的变化趋势。不包括输入数据本身占用的空间,只计算算法运行过程中临时分配的额外空间。

核心要点

代码示例

# 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)。

核心要点

代码示例

# 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)(已知位置时)。

核心要点

代码示例

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 缓存

2.3 栈(Stack)

概念解释

栈是后进先出(LIFO, Last In First Out)的线性数据结构,只允许在栈顶进行插入(push)和删除(pop)操作。栈在函数调用、表达式求值、括号匹配等场景中有广泛应用。

核心要点

代码示例

# 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

2.4 队列(Queue)

概念解释

队列是先进先出(FIFO, First In First Out)的线性数据结构,允许在队尾插入(enqueue),在队首删除(dequeue)。队列在 BFS、任务调度、消息传递等场景中发挥重要作用。

核心要点

代码示例

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、堆、滑动窗口、单调队列

2.5 哈希表(Hash Table)

概念解释

哈希表是基于哈希函数实现的高效查找数据结构,通过键值对(Key-Value)存储数据。理想情况下,插入、删除、查找操作的时间复杂度均为 O(1)。

核心要点

代码示例

# 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

实际应用场景

关联知识点:布隆过滤器、一致性哈希、LRU 缓存


三、树结构

3.1 二叉树(Binary Tree)

概念解释

二叉树是每个节点最多有两个子节点(左子节点和右子节点)的树形数据结构。二叉树是树结构的基础,衍生出二叉搜索树、平衡二叉树、堆等多种变体。

核心要点

代码示例

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 支持高效的查找、插入、删除操作。

核心要点

代码示例

# 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)组成,用于表示多对多的关系。根据边是否有方向,分为有向图和无向图;根据边是否有权重,分为带权图和无权图。

核心要点

代码示例

# 邻接矩阵表示
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)

概念解释

核心要点

代码示例

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(多源最短路径)。

核心要点

代码示例

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 算法
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 之前。常用于任务调度、编译依赖等场景。

核心要点

代码示例

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²),实现简单,适合小规模数据或作为教学入门。包括冒泡排序、选择排序和插入排序。

核心要点

代码示例

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),包括快速排序、归并排序和堆排序。

核心要点

代码示例

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) 线性时间。

核心要点

代码示例

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),是最高效的查找方式之一。

核心要点

代码示例

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) 的查找。核心在于哈希函数设计和冲突处理。

核心要点

代码示例

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 递归与分治

概念解释

核心要点

代码示例

# 归并排序(分治经典)
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 贪心算法

概念解释

贪心算法在每一步都选择当前看起来最优的选项,期望通过局部最优达到全局最优。贪心不一定能得到最优解,但很多经典问题可以。

核心要点

代码示例

# 活动选择问题
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 回溯算法

概念解释

回溯算法通过试探性地构造解,当发现当前路径不可能得到有效解时,回退到上一步继续尝试其他选择。本质是带剪枝的深度优先搜索。

核心要点

代码示例

# 全排列
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
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 背包
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)- 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 等。

核心要点

代码示例

# 最小路径和
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、面试高频题型、打家劫舍系列