Skip to content

时间 & 空间复杂度

时间复杂度空间复杂度是评估算法效率的两个核心指标,帮助我们在不依赖实际运行环境的情况下,量化算法随输入规模增长的资源消耗趋势。

一句话理解

  • 时间复杂度:操作次数如何随 n 增长?
  • 空间复杂度:额外内存消耗如何随 n 增长?
  • 都用 大 O 符号 表示,关注增长趋势,忽略常数系数。

一、时间复杂度

不计算实际运行时间,而是看操作次数的增长趋势。

常见复杂度(从优到劣)

复杂度名称典型算法/场景
O(1)常数时间数组随机访问、哈希表查找
O(log n)对数时间二分查找、平衡二叉树操作
O(n)线性时间遍历数组/链表、线性查找
O(n log n)线性对数时间归并排序、快速排序(平均)
O(n²)平方时间冒泡/选择/插入排序、双重循环
O(2ⁿ)指数时间穷举所有子集、斐波那契递归
O(n!)阶乘时间全排列暴力枚举

增长速度对比

n = 100 时,各复杂度操作次数约为:

O(1)       →          1
O(log n)   →          7
O(n)       →        100
O(n log n) →        664
O(n²)      →     10,000
O(2ⁿ)      → 1,267,650,600,228,229,401,496,703,205,376  ← 完全不可用

代码示例

python
# O(1) — 无论 n 多大,操作次数固定
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) — 操作次数与 n 成正比
def linear_scan(arr):
    for item in arr:
        print(item)

# 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(n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]

如何推导时间复杂度

python
# 技巧:找出执行次数最多的那段代码,看它和 n 的关系

# 单层循环 → O(n)
for i in range(n):
    pass

# 双层循环 → O(n²)
for i in range(n):
    for j in range(n):
        pass

# 每次折半 → O(log n)
i = n
while i > 1:
    i = i // 2

# 外层 O(n),内层 O(log n) → 合计 O(n log n)
for i in range(n):
    j = n
    while j > 1:
        j = j // 2

计算规则

  1. 忽略常数O(2n)O(n)O(100)O(1)
  2. 取最高阶O(n² + n)O(n²)
  3. 乘法法则:嵌套循环,复杂度相乘
  4. 加法法则:顺序执行,取最大值

二、空间复杂度

计算算法运行时占用的"额外"内存,不包括输入数据本身。

常见复杂度

复杂度名称典型场景
O(1)常数空间原地排序、交换两个变量
O(log n)对数空间递归二分查找(调用栈深度)
O(n)线性空间复制数组、哈希表、递归遍历
O(n²)平方空间二维矩阵、邻接矩阵存图

代码示例

python
# O(1) — 只用了固定数量的变量,与 n 无关
def swap(a, b):
    a, b = b, a
    return a, b

# O(1) — 原地反转,不申请新数组
def reverse_in_place(arr):
    left, right = 0, len(arr) - 1
    while left < right:
        arr[left], arr[right] = arr[right], arr[left]
        left += 1
        right -= 1

# O(n) — 申请了一个与 n 等长的新数组
def copy_array(arr):
    return [x for x in arr]

# O(n) — 递归调用栈深度为 n
def factorial(n):
    if n <= 1:
        return 1
    return n * factorial(n - 1)   # 调用栈会积累 n 层

# O(n²) — 生成 n×n 的二维矩阵
def create_matrix(n):
    return [[0] * n for _ in range(n)]

递归的空间复杂度

递归函数每次调用都会在调用栈上压入一帧,因此:

  • 递归深度为 n → 空间复杂度 O(n)
  • 递归深度为 log n(如二分递归)→ 空间复杂度 O(log n)
  • 尾递归优化后栈帧复用 → 理论上可降到 O(1)(Python 不支持,Java/C 支持)

三、时间 vs 空间的权衡

两者往往是矛盾的 — 想跑得更快,通常需要多占内存;想省内存,通常需要多花时间。

策略含义典型例子
空间换时间用更多内存换取更快的速度哈希表、缓存(Memoization)、预计算查表
时间换空间减少内存使用,接受更慢的速度流式处理大文件、迭代替代递归

典型案例

案例一:哈希表(空间换时间)

python
# 暴力查找两数之和 — O(n²) 时间,O(1) 空间
def two_sum_brute(nums, target):
    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i, j]

# 哈希表优化 — O(n) 时间,O(n) 空间(用空间换了时间)
def two_sum_hash(nums, target):
    seen = {}                          # 额外 O(n) 空间
    for i, num in enumerate(nums):
        complement = target - num
        if complement in seen:         # O(1) 查找
            return [seen[complement], i]
        seen[num] = i

案例二:斐波那契(Memoization 缓存)

python
# 纯递归 — O(2ⁿ) 时间,大量重复计算
def fib_naive(n):
    if n <= 1:
        return n
    return fib_naive(n - 1) + fib_naive(n - 2)

# 记忆化递归 — O(n) 时间,O(n) 空间(缓存中间结果)
from functools import lru_cache

@lru_cache(maxsize=None)
def fib_memo(n):
    if n <= 1:
        return n
    return fib_memo(n - 1) + fib_memo(n - 2)

# 迭代 — O(n) 时间,O(1) 空间(时间换空间的反例:这里两者都优)
def fib_iter(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a

工程实践中的取舍原则

  • 优先优化时间复杂度:内存便宜,CPU 时间是用户体验的直接感知
  • 内存受限时关注空间:嵌入式、移动端、大数据场景内存是瓶颈
  • 缓存是最常见的空间换时间:Redis、本地缓存、CDN 本质都是这个思路
  • 流式处理是最常见的时间换空间:处理 TB 级文件时不可能全部加载进内存

四、常见算法复杂度速查

算法时间(最好)时间(平均)时间(最坏)空间
冒泡排序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 log n)O(n log n)O(n log n)O(n)
快速排序O(n log n)O(n log n)O(n²)O(log n)
堆排序O(n log n)O(n log n)O(n log n)O(1)
二分查找O(1)O(log n)O(log n)O(1)
哈希表查找O(1)O(1)O(n)*O(n)
DFS / BFSO(V+E)O(V+E)O(V)

* 哈希表最坏情况为所有 key 哈希碰撞退化为链表,实际极少发生。