【算法图解】空间复杂度:内存也是钱

时间复杂度大家都知道,但空间复杂度同样重要。小明带你理解算法对内存的消耗,以及如何在时间和空间之间做权衡。

7 分钟阅读
小明

【算法图解】空间复杂度:内存也是钱

在之前的时间复杂度文章里,我们讨论的是算法"跑多快"的问题。但一个算法的好坏,不仅看它跑得快不快,还要看它占多大地方

想象一下,你写了一个超快的算法,但它需要 100GB 的内存。结果你的服务器只有 16GB。这时候,再快也没用——程序直接 Out of Memory 崩掉了。

今天,小明就来聊聊算法的另一半故事:空间复杂度(Space Complexity)


一、什么是空间复杂度?

空间复杂度描述的是:算法在运行过程中,临时占用的额外存储空间与输入规模 n 之间的关系。

注意关键词:额外临时

  • 输入数据本身占用的空间,通常不算在内(那是你逃不掉的)。
  • 我们关心的是算法运行时额外申请的空间。

二、常见的空间复杂度

复杂度含义例子
O(1)常数空间不管数据多大,只需要几个固定的变量
O(n)线性空间需要一个和输入同等规模的数组
O(n²)平方空间需要一个二维矩阵
O(log n)对数空间递归栈的深度(如二分查找)

三、实战分析

3.1 O(1) 空间:原地修改

function swap(arr, i, j) {
  const temp = arr[i];
  arr[i] = arr[j];
  arr[j] = temp;
}

这里只用到了一个临时变量 temp,不管数组多大,额外空间都是固定的。

3.2 O(n) 空间:需要辅助数组

function copyArray(arr) {
  const newArr = [];
  for (const item of arr) {
    newArr.push(item);
  }
  return newArr;
}

我们创建了一个和原数组一样大的新数组,所以空间复杂度是 O(n)。

3.3 递归的空间复杂度

递归是个容易被忽视的空间杀手。每次递归调用都会在栈上分配一块空间(保存局部变量、返回地址等)。

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

这个阶乘函数,会递归 n 层,所以空间复杂度是 O(n)


四、时间 vs 空间:你只能选一个?

很多时候,时间和空间是此消彼长的关系。

例子:斐波那契数列

  1. 递归版:时间 O(2^n),空间 O(n)(栈深)—— 时间爆炸。
  2. 记忆化递归:时间 O(n),空间 O(n)(缓存 + 栈)—— 用空间换时间。
  3. 迭代版:时间 O(n),空间 O(1)—— 时间和空间都最优。

这就是算法优化的艺术:在约束条件下,找到最佳的平衡点。


总结

  1. 空间复杂度衡量算法运行时额外占用的内存。
  2. O(1) 是最理想的,意味着原地处理。
  3. 递归会消耗栈空间,深度为 n 的递归空间复杂度至少是 O(n)。
  4. 时间和空间的权衡是算法设计的核心问题之一。

小明建议: 面试时,如果面试官问你算法复杂度,不要只回答时间复杂度,把空间复杂度也一起说了。这会让你显得更专业。


"时间就是金钱,空间也是金钱。" "所以写出一个时间空间双优的算法,就是在帮公司省钱。" —— 小明

最后,送你一个冷笑话: 一个算法工程师去面试。 面试官问:"你的算法时间复杂度是多少?" 工程师:"O(1)。" 面试官:"空间复杂度呢?" 工程师:"O(n²)……" 面试官:"那你是开了个仓库来存东西啊。" 工程师:"……我存的全是对甲方的怨念。"