CS辅导算法Big-O动态规划期末备考编程辅导

算法课高频考点完全指南:Big-O 分析/排序/动态规划/回溯

5 min read

算法课高频考点完全指南

算法课(通常叫 Algorithm Design、Algorithms、Introduction to Algorithms)是 CS 本科和研究生最重要的必修课之一,也是大厂 Coding Interview 的直接考核内容。

但很多学生在算法课上的困境是:看懂了,写不出来;能写出来,但 Big-O 分析不对;Big-O 能算,但 Dynamic Programming 的 Recurrence 写不出来

这篇指南聚焦四个最高频考点,给出可以直接用的框架。


一、Big-O 复杂度分析

Big-O 分析是算法课每道题的必答部分,分析错误会失去 10–30% 的分数。

常见复杂度排序(从快到慢)

O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ) < O(n!)

递归复杂度的 Master Theorem

对于 T(n) = aT(n/b) + f(n) 形式的递归:

条件结论
f(n) = O(n^(log_b(a) - ε))T(n) = Θ(n^log_b(a))
f(n) = Θ(n^log_b(a))T(n) = Θ(n^log_b(a) · log n)
f(n) = Ω(n^(log_b(a) + ε))T(n) = Θ(f(n))

常用记忆

  • 归并排序:T(n) = 2T(n/2) + O(n) → O(n log n)
  • 二分搜索:T(n) = T(n/2) + O(1) → O(log n)
  • 快速排序平均:T(n) = 2T(n/2) + O(n) → O(n log n)(但最坏 O(n²))

易错点

循环嵌套不一定是 O(n²)

# 这是 O(n) 而不是 O(n²)!
i = 0
while i < n:
    j = i
    while j < n:
        j *= 2  # j 每次翻倍,内层是 O(log n)
    i += 1
# 外层 O(n),内层 O(log n),总体 O(n log n)

空间复杂度别忘了递归栈

  • DFS 递归的递归栈深度 = O(树的深度) = O(log n)(平衡树)或 O(n)(退化链表)

二、排序算法

各排序算法对比

算法时间(平均)时间(最坏)空间稳定
Bubble SortO(n²)O(n²)O(1)
Selection SortO(n²)O(n²)O(1)
Insertion SortO(n²)O(n²)O(1)
Merge SortO(n log n)O(n log n)O(n)
Quick SortO(n log n)O(n²)O(log n)
Heap SortO(n log n)O(n log n)O(1)
Counting SortO(n + k)O(n + k)O(k)

考试常考的问题

  • 为什么 Quick Sort 最坏 O(n²)?(每次 pivot 选到最大/最小值,退化为 n 层递归)
  • 为什么 Merge Sort 总是 O(n log n)?(每次确定性二分,不依赖数据分布)
  • 什么情况下 Insertion Sort 是 O(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)

def merge(left, right):
    result = []
    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

三、动态规划(Dynamic Programming)

动态规划是算法课最难的部分,也是期末考试分值最高的题型(通常占 30–40%)。

DP 解题四步框架

  1. 定义状态(State Definition)dp[i]dp[i][j] 代表什么?
  2. 写出转移方程(Recurrence)dp[i] 如何从前面的状态推导?
  3. 确定初始值(Base Cases)dp[0]dp[i][0] 等边界值
  4. 确定计算顺序(Iteration Order):从哪个方向填表?

经典 DP 问题

1. 最长上升子序列(LIS - Longest Increasing Subsequence)

def length_of_lis(nums):
    n = len(nums)
    dp = [1] * n  # dp[i] = 以 nums[i] 结尾的 LIS 长度
    for i in range(1, n):
        for j in range(i):
            if nums[j] < nums[i]:
                dp[i] = max(dp[i], dp[j] + 1)
    return max(dp)
# 时间 O(n²),可优化到 O(n log n)

2. 0/1 背包问题(0/1 Knapsack)

def knapsack(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(capacity + 1):
            # 不选第 i 件:dp[i-1][w]
            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]

3. 最长公共子序列(LCS - Longest Common Subsequence)

def lcs(s1, s2):
    m, n = len(s1), len(s2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if s1[i-1] == s2[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]

4. 硬币找零(Coin Change)

def coin_change(coins, amount):
    dp = [float('inf')] * (amount + 1)
    dp[0] = 0
    for i in range(1, amount + 1):
        for coin in coins:
            if coin <= i:
                dp[i] = min(dp[i], dp[i - coin] + 1)
    return dp[amount] if dp[amount] != float('inf') else -1

DP 考试失分点

  1. 状态定义不清楚:写 Recurrence 之前必须先明确 dp[i] 的含义,否则转移方程无法成立
  2. Base Case 漏掉:所有 DP 题必须明确边界初始值
  3. 迭代顺序搞反:有些 DP 需要从右到左填表(滚动数组优化时特别注意)
  4. 只给 Recurrence,不给最终答案dp[n] 不一定是答案,LIS 的答案是 max(dp)

四、回溯法(Backtracking)

回溯法适用于:排列组合、子集、数独、N 皇后等穷举问题。

回溯代码模板

def backtrack(path, choices, result):
    # 终止条件(Base Case)
    if 满足终止条件:
        result.append(path[:])  # 注意要拷贝!
        return
    
    for choice in choices:
        # 做选择
        path.append(choice)
        # 递归
        backtrack(path, updated_choices, result)
        # 撤销选择(回溯)
        path.pop()

经典回溯题

全排列(Permutations)

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

子集(Subsets)

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

考试最后 1 周的复习清单

  • 能默写归并排序并分析 O(n log n)
  • 能用 Master Theorem 分析递归 T(n)
  • 能写 BFS/DFS 模板(图和树两种)
  • 能用四步框架分析任意 DP 题
  • 能写 0/1 背包、LCS、LIS 代码
  • 能用回溯模板解决排列/子集问题
  • 了解 NP-Hard / NP-Complete 的基本概念(理论课考点)

如果算法课让你感到力不从心,欢迎联系我们的 CS 辅导团队——无论是 Problem Set 卡住了还是期末考前需要系统梳理,我们都能提供针对性支持。

相关院校算法课辅导: UIUC CS 374 · UCLA CS 180 · Columbia COMS 4231 · UToronto CSC373 · UNSW COMP3121

💻

代码跑不通?作业逻辑卡住了?

Deadline 前搞定。发送代码/题目给客服,30 分钟内评估,安排 CS 专业导师。

扫码咨询发 Brief · 30 分钟报价