面试被问复杂度总懵?这篇指南帮你彻底搞清

时间复杂度与空间复杂度计算指南

目录


基本概念

复杂度衡量的是算法性能随数据量 n 增长的趋势,不是真实时间或内存。

用大 O 表示法(Big O)描述上界:

scss 复制代码
O(1) < O(logn) < O(n) < O(nlogn) < O(n²) < O(n³) < O(2ⁿ) < O(n!)

化简规则

规则 示例
去掉系数 O(3n) → O(n)
只保留最高次项 O(n² + n) → O(n²)
常数统一为 O(1) O(100) → O(1)

时间复杂度

O(1):常数时间

执行次数固定,不随 n 变化。

java 复制代码
int a = arr[0];           // 直接访问
int b = arr[n - 1];       // 下标访问
map.get(key);             // 哈希表查找

O(logn):对数时间

每次问题规模折半(或缩小为固定比例)。

java 复制代码
// 二分查找
while (left <= right) {
    int mid = (left + right) / 2;
    if (arr[mid] == target) return mid;
    else if (arr[mid] < target) left = mid + 1;
    else right = mid - 1;
}
// n → n/2 → n/4 → ... → 1,共 log₂n 次
java 复制代码
// 求整数位数
while (n > 0) {
    n /= 10;   // 每次除以10,共 log₁₀n 次
}

O(n):线性时间

遍历一次数据。

java 复制代码
// 单层循环
for (int i = 0; i < n; i++) { }

// 链表遍历
while (node != null) { node = node.next; }

// 双指针(两个指针各走一次)
int left = 0, right = n - 1;
while (left < right) { left++; right--; }

O(nlogn):线性对数时间

外层 n 次,内层 logn 次,或分治每层 O(n)。

java 复制代码
// 归并排序:logn 层,每层处理 n 个元素
void mergeSort(int[] arr, int l, int r) {
    mergeSort(arr, l, mid);
    mergeSort(arr, mid+1, r);
    merge(arr, l, mid, r);   // O(n)
}

// 堆排序:n 次堆化,每次 O(logn)
for (int i = n-1; i > 0; i--) {
    swap(arr, 0, i);
    heapify(arr, i, 0);      // O(logn)
}

O(n²):平方时间

两层嵌套循环。

java 复制代码
// 冒泡排序
for (int i = 0; i < n; i++)
    for (int j = 0; j < n-i-1; j++)
        if (arr[j] > arr[j+1]) swap(arr, j, j+1);

// 注意:内层不从0开始,仍是 O(n²)
// n + (n-1) + (n-2) + ... + 1 = n(n+1)/2 = O(n²)

O(n³):立方时间

三层嵌套循环。

java 复制代码
// Floyd 最短路径
for (int k = 0; k < n; k++)
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);

O(2ⁿ):指数时间

每次分裂为两个子问题,递归树节点数翻倍。

java 复制代码
// 斐波那契朴素递归
int fib(int n) {
    if (n <= 1) return n;
    return fib(n-1) + fib(n-2);
}

// 子集枚举
void subsets(int[] nums, int idx, List<Integer> path) {
    result.add(new ArrayList<>(path));
    for (int i = idx; i < nums.length; i++) {
        path.add(nums[i]);
        subsets(nums, i+1, path);
        path.remove(path.size()-1);
    }
}

O(n!):阶乘时间

全排列、旅行商问题暴力解。

java 复制代码
// 全排列
void permute(int[] nums, int start) {
    if (start == nums.length) { result.add(...); return; }
    for (int i = start; i < nums.length; i++) {
        swap(nums, start, i);
        permute(nums, start+1);
        swap(nums, start, i);
    }
}
// 共 n! 种排列

特殊情况

两段独立循环:O(n + m)
java 复制代码
for (int i = 0; i < n; i++) { }   // O(n)
for (int i = 0; i < m; i++) { }   // O(m)

// 总复杂度 O(n + m),若 m >> n 则 O(m)
循环次数非显而易见
java 复制代码
// 看起来是 O(n²),实际是 O(nlogn)
for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j += i) {  // j 每次加 i
        // 执行次数:n/1 + n/2 + n/3 + ... + n/n
        // = n × (1 + 1/2 + 1/3 + ... + 1/n)
        // = n × H(n) ≈ n × lnn = O(nlogn)
    }
}
java 复制代码
// 看起来两层,实际 O(n)
// 因为 i 和 j 合计只走 n 步
int i = 0, j = 0;
while (i < n && j < n) {
    if (condition) i++;
    else j++;
}

空间复杂度

计算原则

复制代码
不算:输入数组本身(题目给定的)
算:  代码中额外申请的变量、数组、递归调用栈

O(1):常数空间

只用了固定数量的变量。

java 复制代码
// 原地操作,没有额外申请数组
int sum = 0;
for (int num : arr) sum += num;

// 双指针原地反转
int left = 0, right = n - 1;
while (left < right) swap(arr, left++, right--);

O(n):线性空间

申请了和 n 相关的额外空间。

java 复制代码
int[] temp = new int[n];          // 额外数组
Map<Integer, Integer> map = ...;  // 哈希表存 n 个元素
List<Integer> result = ...;       // 结果列表

O(n²):平方空间

二维数组或邻接矩阵。

java 复制代码
int[][] dp = new int[n][n];       // DP 表
int[][] graph = new int[n][n];    // 邻接矩阵

O(logn):对数空间(递归栈)

递归深度为 logn。

java 复制代码
// 二分查找递归版,深度 logn
int binarySearch(int[] arr, int l, int r, int target) {
    int mid = (l + r) / 2;
    return binarySearch(arr, l, mid-1, target);
}

// 快速排序平均情况,深度 logn
void quickSort(int[] arr, int l, int r) {
    int p = partition(arr, l, r);
    quickSort(arr, l, p-1);
    quickSort(arr, p+1, r);
}

递归栈空间计算

scss 复制代码
空间 = 递归深度 × 每帧大小
每帧存储:参数 + 局部变量 + 返回地址 → 通常是 O(1)
所以:空间复杂度 = O(递归深度)
场景 递归深度 空间复杂度
二分查找 logn O(logn)
快速排序(平均) logn O(logn)
快速排序(最坏) n O(n)
归并排序 logn(栈)+ n(数组) O(n)
斐波那契朴素递归 n O(n)
DFS 树遍历 树高 h O(h)

递归的复杂度

计算公式

复制代码
时间 = 递归调用总次数 × 每次调用的工作量
空间 = 递归深度(树的高度)× 每帧大小

递归树分析法

画出递归调用树,计算:

  • 树的节点总数 = 总调用次数
  • 每个节点的工作量
  • 树的高度 = 递归深度
scss 复制代码
fib(4) 的递归树:

              fib(4)              层0:1个节点
            /        \
        fib(3)       fib(2)       层1:2个节点
        /    \       /    \
    fib(2) fib(1) fib(1) fib(0)  层2:4个节点

总节点数 ≈ 2⁴ = 16 → 时间 O(2ⁿ)
树高    = n      → 空间 O(n)

主定理(Master Theorem)

适用于形如 T(n) = aT(n/b) + f(n) 的递归:

scss 复制代码
a = 子问题个数
b = 每次规模缩小比例
f(n) = 每层额外工作量

情况1:f(n) < n^(log_b a)  → T(n) = O(n^(log_b a))
情况2:f(n) = n^(log_b a)  → T(n) = O(n^(log_b a) × logn)
情况3:f(n) > n^(log_b a)  → T(n) = O(f(n))

实例:

scss 复制代码
归并排序:T(n) = 2T(n/2) + O(n)
  a=2, b=2, f(n)=n, n^(log₂2)=n¹=n
  f(n) = n^(log_b a) → 情况2
  T(n) = O(nlogn) ✅

二分查找:T(n) = T(n/2) + O(1)
  a=1, b=2, f(n)=1, n^(log₂1)=n⁰=1
  f(n) = n^(log_b a) → 情况2
  T(n) = O(logn) ✅

三路归并:T(n) = 3T(n/3) + O(n)
  a=3, b=3, f(n)=n, n^(log₃3)=n
  f(n) = n^(log_b a) → 情况2
  T(n) = O(nlogn) ✅

常见算法复杂度速查

排序算法

算法 平均时间 最坏时间 最好时间 空间 稳定
冒泡排序 O(n²) O(n²) O(n) O(1)
选择排序 O(n²) O(n²) O(n²) O(1)
插入排序 O(n²) O(n²) O(n) O(1)
希尔排序 O(n^1.3) O(n²) O(n) O(1)
归并排序 O(nlogn) O(nlogn) O(nlogn) O(n)
快速排序 O(nlogn) O(n²) O(nlogn) O(logn)
堆排序 O(nlogn) O(nlogn) O(nlogn) O(1)
计数排序 O(n+k) O(n+k) O(n+k) O(k)
基数排序 O(d×n) O(d×n) O(d×n) O(n)
桶排序 O(n+k) O(n²) O(n) O(n)

数据结构操作

数据结构 访问 查找 插入 删除
数组 O(1) O(n) O(n) O(n)
链表 O(n) O(n) O(1) O(1)
栈 / 队列 O(n) O(n) O(1) O(1)
哈希表 --- O(1) 均摊 O(1) 均摊 O(1) 均摊
二叉搜索树(平衡) O(logn) O(logn) O(logn) O(logn)
二叉搜索树(退化) O(n) O(n) O(n) O(n)
O(1) 取顶 O(n) O(logn) O(logn)
前缀树 Trie O(m) O(m) O(m) O(m)

m 为字符串长度

图算法

算法 时间 空间
BFS / DFS O(V+E) O(V)
Dijkstra(堆优化) O((V+E)logV) O(V)
Bellman-Ford O(V×E) O(V)
Floyd-Warshall O(V³) O(V²)
Prim(堆优化) O((V+E)logV) O(V)
Kruskal O(ElogE) O(V)
拓扑排序 O(V+E) O(V)

动态规划常见模式

问题类型 时间 空间 滚动数组优化后
线性 DP(爬楼梯) O(n) O(n) O(1)
0-1 背包 O(n×W) O(n×W) O(W)
完全背包 O(n×W) O(n×W) O(W)
最长公共子序列 LCS O(m×n) O(m×n) O(min(m,n))
最长递增子序列 LIS O(n²) / O(nlogn) O(n) ---
区间 DP O(n³) O(n²) ---
树形 DP O(n) O(n) ---

复杂度判断口诀

scss 复制代码
无循环              → O(1)
一层循环            → O(n)
两层嵌套循环        → O(n²)
每次折半            → O(logn)
一层循环 + 每次折半 → O(nlogn)
三层嵌套循环        → O(n³)

递归看树:
  总节点数 × 每节点工作量 → 时间复杂度
  树的高度               → 空间复杂度

分治看主定理:
  T(n) = aT(n/b) + f(n)
  比较 f(n) 和 n^(log_b a) 的大小决定结果

陷阱:
  看起来两层但指针不重叠 → 实际 O(n)
  看起来一层但步长非1   → 实际 O(nlogn) 或更低
  递归不一定是 O(n)     → 要画递归树分析
相关推荐
knight_9___2 小时前
大模型project面试4
人工智能·python·深度学习·算法·面试·agent
l1t2 小时前
DeepSeek总结的欢迎来到 ORDER BY 丛林
数据库·算法
谙弆悕博士2 小时前
【附C源码】二叉搜索树的C语言实现
c语言·开发语言·数据结构·算法·二叉树·项目实战·数据结构与算法
宵时待雨2 小时前
回溯算法专题2:二叉树中的深搜
开发语言·数据结构·c++·笔记·算法·深度优先
刀法如飞3 小时前
JavaScript 数组去重的 20 种实现方式,学会用不同思路解决问题
前端·javascript·算法
洛水水3 小时前
【力扣100题】46.单词拆分
算法·leetcode·职场和发展
MicroTech20253 小时前
量子安全赋能协同智能,微算法科技(NASDAQ :MLGO)研发PQS-BFL后量子区块链联邦学习框架
科技·算法·安全
平行侠3 小时前
A19 工业设备故障决策树智能诊断系统
算法·决策树·机器学习
铮铭4 小时前
【论文阅读】世界模型发展脉络整理---Understanding World or Predicting Future? A Comprehensive Survey of World Models
论文阅读·人工智能·算法·机器人