复杂度分析方法

时间复杂度与空间复杂度分析

刷题时每道题都要写复杂度,但很多人只是背结论,不知道怎么推出来。这篇把分析方法说清楚,后续题解里的复杂度分析都以此为基础。


一、复杂度是什么

复杂度衡量的是算法随输入规模 nnn 增长时,资源消耗的增长趋势,而不是具体的运行时间或内存字节数。

  • 时间复杂度 :操作次数随 nnn 的增长趋势
  • 空间复杂度 :额外内存占用随 nnn 的增长趋势

用大 O 表示法 O(f(n))O(f(n))O(f(n)) 描述,只保留增长最快的项,忽略常数系数。

O(3n2+5n+100)=O(n2)O(3n^2 + 5n + 100) = O(n^2)O(3n2+5n+100)=O(n2)


二、常见复杂度从低到高

O(1)<O(log⁡n)<O(n)<O(nlog⁡n)<O(n2)<O(2n)<O(n!)O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!)O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(2n)<O(n!)

复杂度 n=10n=10n=10 n=100n=100n=100 n=106n=10^6n=106 典型场景
O(1)O(1)O(1) 1 1 1 哈希表查询
O(log⁡n)O(\log n)O(logn) 3 7 20 二分查找
O(n)O(n)O(n) 10 100 10610^6106 线性扫描
O(nlog⁡n)O(n \log n)O(nlogn) 33 664 2×1072×10^72×107 排序
O(n2)O(n^2)O(n2) 100 10410^4104 101210^{12}1012 暴力双层循环
O(2n)O(2^n)O(2n) 1024 103010^{30}1030 爆炸 暴力枚举子集

力扣题目 nnn 通常在 104∼10510^4 \sim 10^5104∼105,O(n2)O(n^2)O(n2) 一般会超时,O(nlog⁡n)O(n \log n)O(nlogn) 及以下基本没问题。


三、时间复杂度:怎么数操作次数

3.1 单层循环 → O(n)O(n)O(n)

cpp 复制代码
for (int i = 0; i < n; i++) {
    // 常数次操作
}

循环体执行 nnn 次,每次 O(1)O(1)O(1),总计 O(n)O(n)O(n)。


3.2 嵌套循环 → O(n2)O(n^2)O(n2)

cpp 复制代码
for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        // 常数次操作
    }
}

外层 nnn 次,内层每次也 nnn 次,总计 n×n=O(n2)n \times n = O(n^2)n×n=O(n2)。

如果内层只跑到 iii:

cpp 复制代码
for (int i = 0; i < n; i++) {
    for (int j = 0; j < i; j++) { ... }
}

总操作数 =0+1+2+⋯+(n−1)=n(n−1)2= 0 + 1 + 2 + \cdots + (n-1) = \dfrac{n(n-1)}{2}=0+1+2+⋯+(n−1)=2n(n−1),忽略常数仍是 O(n2)O(n^2)O(n2)。


3.3 每次折半 → O(log⁡n)O(\log n)O(logn)

cpp 复制代码
int x = n;
while (x > 1) {
    x /= 2;
}

每次 xxx 减半,执行次数为 log⁡2n\log_2 nlog2n,即 O(log⁡n)O(\log n)O(logn)。

二分查找的复杂度由此而来:每次排除一半区间,最多执行 log⁡2n\log_2 nlog2n 次。


3.4 排序的代价

调用 sort() 的时间复杂度固定是 O(nlog⁡n)O(n \log n)O(nlogn),这是基于比较的排序的理论下界。

如果你的算法里有一步排序,整体至少是 O(nlog⁡n)O(n \log n)O(nlogn),不可能更低。


3.5 递归:看递归树

递归的复杂度取决于递归次数 × 每次的工作量

以二分查找为例:

cpp 复制代码
int binarySearch(vector<int>& nums, int left, int right, int target) {
    if (left > right) return -1;
    int mid = left + (right - left) / 2;
    if (nums[mid] == target) return mid;
    else if (nums[mid] < target) return binarySearch(nums, mid + 1, right, target);
    else return binarySearch(nums, left, mid - 1, target);
}

每次递归规模减半,递归深度 O(log⁡n)O(\log n)O(logn),每层工作量 O(1)O(1)O(1),总计 O(log⁡n)O(\log n)O(logn)。

以归并排序为例:每次分成两半,递归深度 O(log⁡n)O(\log n)O(logn),每层合并工作量 O(n)O(n)O(n),总计 O(nlog⁡n)O(n \log n)O(nlogn)。


3.6 摊还分析:push_back 为什么是 O(1)O(1)O(1)

vectorpush_back 偶尔会触发扩容(申请新内存 + 拷贝所有元素),单次最坏 O(n)O(n)O(n)。但扩容是按倍数增长的(1→2→4→8→...),nnn 次 push_back 触发扩容的总拷贝次数不超过 2n2n2n,平均每次 O(1)O(1)O(1)。

这种"单次最坏但均摊下来是常数"的分析叫摊还分析 ,结论就是 push_back 均摊 O(1)O(1)O(1)。


四、空间复杂度:只算额外空间

空间复杂度只统计算法额外申请的空间,输入本身占用的空间不算在内。

4.1 开了一个和 nnn 等长的数组 → O(n)O(n)O(n)

cpp 复制代码
vector<int> dp(n, 0);  // 额外申请 n 个元素

4.2 只用了几个变量 → O(1)O(1)O(1)

cpp 复制代码
int left = 0, right = n - 1;  // 无论 n 多大,额外空间恒定

4.3 递归调用栈 → O(递归深度)O(递归深度)O(递归深度)

递归每深入一层就占用一帧栈空间。

  • 二分查找递归深度 O(log⁡n)O(\log n)O(logn),空间 O(log⁡n)O(\log n)O(logn)
  • 遍历一棵高度为 hhh 的二叉树,空间 O(h)O(h)O(h),最坏(链状树)O(n)O(n)O(n)

这也是为什么能用迭代替代递归时,迭代的空间复杂度更优(O(1)O(1)O(1) vs O(log⁡n)O(\log n)O(logn))。


五、快速判断模板

拿到一段代码,按下面的顺序判断:

复制代码
1. 有没有循环?
   - 单层循环 n 次 → O(n)
   - 双层嵌套 → O(n²),注意内层是否真的跑 n 次
   - 循环变量每次折半 → O(log n)

2. 有没有调用 sort?
   - 直接加一个 O(n log n)

3. 有没有递归?
   - 估算递归深度 × 每层工作量

4. 取所有部分里增长最快的那个作为最终答案

六、例子:两数之和(Hot100 第一题)

cpp 复制代码
unordered_map<int, int> mp;
for (int i = 0; i < nums.size(); i++) {
    int complement = target - nums[i];
    if (mp.count(complement)) return {mp[complement], i};
    mp[i] = nums[i];  // 注意这里存的是下标
}
  • 时间复杂度 :单层循环 nnn 次,每次哈希查询 O(1)O(1)O(1),总计 O(n)O(n)O(n)
  • 空间复杂度 :哈希表最多存 nnn 个元素,O(n)O(n)O(n)

对比暴力双层循环:时间 O(n2)O(n^2)O(n2),空间 O(1)O(1)O(1)。用空间换时间,这是哈希表的核心思路。


小结

复杂度分析不需要精确计算操作次数,只需要判断增长趋势。记住几个核心规律:

  • 折半操作 → log⁡n\log nlogn
  • 线性扫描 → nnn
  • 嵌套循环 → n2n^2n2
  • 排序 → nlog⁡nn \log nnlogn
  • 递归 → 深度 × 每层代价
相关推荐
科研前沿1 小时前
2026 数字孪生前沿科技:全景迭代报告 —— 镜像视界生成式孪生(Generative DT)技术白皮书
大数据·人工智能·科技·算法·音视频·空间计算
学涯乐码堂主3 小时前
有趣的“打擂台算法”
c++·算法·青少年编程·gesp
Tutankaaa4 小时前
知识竞赛题库设计全攻略
人工智能·算法
WolfGang0073214 小时前
代码随想录算法训练营 Day50 | 图论 part08
数据结构·算法·图论
aini_lovee6 小时前
多目标粒子群优化(MOPSO)双适应度函数MATLAB实现
人工智能·算法·matlab
yong99906 小时前
图像融合与拼接:完整MATLAB工具箱
算法·计算机视觉·matlab
春风不语5056 小时前
深入理解主成分分析(PCA)
算法
apollowing6 小时前
启发式算法WebApp实验室:从搜索策略到群体智能的能力进阶(二十二)
算法·启发式算法·web app
晚枫歌F6 小时前
最小堆定时器
数据结构·算法