【数据结构与算法】空间复杂度从入门到面试:不仅会算,还要会解释

👨‍💻 关于作者:会编程的土豆

"不是因为看见希望才坚持,而是坚持了才看见希望。"

你好,我是会编程的土豆,一名热爱后端技术的Java学习者。

📚 正在更新中的专栏:

💕作者简介:后端学习者

很多人学算法时都会有一个真实问题:时间复杂度还能勉强分析,但空间复杂度一问就容易卡住,尤其一遇到递归、容器就开始不确定。其实空间复杂度并不难,它的核心只有一句话:程序运行过程中,额外占用了多少随输入规模 n 增长的内存

这篇文章不讲空话,直接结合代码,把空间复杂度一层一层讲清楚。


一、最基础:什么是空间复杂度

先看一个最简单的例子:

cpp 复制代码
int sum(int n) {
    int s = 0;
    for (int i = 1; i <= n; i++) {
        s += i;
    }
    return s;
}

这段代码:

  • 只用了 si 两个变量

  • 不管 n 多大,变量数量不变

所以空间复杂度是:

复制代码
O(1)

二、空间"跟着 n 变"的情况

再看一个例子:

cpp 复制代码
vector<int> buildArray(int n) {
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        a[i] = i;
    }
    return a;
}

这里关键点:

  • vector<int> a(n) 开辟了 n 个空间

  • n 越大,占用空间越大

所以:

复制代码
空间复杂度 = O(n)

三、二维情况:更高维空间

复制代码
vector<vector<int>> matrix(int n) {
    vector<vector<int>> a(n, vector<int>(n));
    return a;
}

这里开了一个 n × n 的二维数组:

复制代码
空间复杂度 = O(n²)

四、最容易忽略的重点:递归栈

来看一个经典递归:

复制代码
int dfs(int n) {
    if (n == 0) return 0;
    return dfs(n - 1) + 1;
}

这个函数会:

复制代码
dfs(n) → dfs(n-1) → dfs(n-2) → ... → dfs(0)

一共调用 n 层,每一层都会占用栈空间。

所以:

复制代码
空间复杂度 = O(n)

再看一个更典型的例子(二叉树)

复制代码
int maxDepth(TreeNode* root) {
    if (!root) return 0;
    return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}

这里递归深度取决于树的高度:

  • 平衡树:高度 ≈ log n → O(log n)

  • 极端链式树:高度 = n → O(n)


五、辅助数据结构的空间

比如 BFS:

cpp 复制代码
vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> res;
    if (!root) return res;

    queue<TreeNode*> q;
    q.push(root);

    while (!q.empty()) {
        int n = q.size();
        vector<int> level;

        for (int i = 0; i < n; i++) {
            TreeNode* node = q.front();
            q.pop();
            level.push_back(node->val);

            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }

        res.push_back(level);
    }
    return res;
}

这里用到了:

  • 队列 queue

  • 结果数组 res

最坏情况下:

复制代码
空间复杂度 = O(n)

六、经典案例:堆排序的空间复杂度

很多人会误判这个题。

代码(递归版 heapify)

cpp 复制代码
void heapify(vector<int>& arr, int i, int heapSize) {
    int largest = i;
    int left = 2*i + 1;
    int right = 2*i + 2;

    if (left < heapSize && arr[left] > arr[largest])
        largest = left;
    if (right < heapSize && arr[right] > arr[largest])
        largest = right;

    if (largest != i) {
        swap(arr[i], arr[largest]);
        heapify(arr, largest, heapSize);
    }
}

分析:

  • 没有开新数组 → O(1)

  • 但是有递归 → 深度 ≈ log n

所以:

复制代码
空间复杂度 = O(log n)

优化:改成迭代写法

cpp 复制代码
void heapify(vector<int>& arr, int i, int heapSize) {
    while (true) {
        int largest = i;
        int left = 2*i + 1;
        int right = 2*i + 2;

        if (left < heapSize && arr[left] > arr[largest])
            largest = left;
        if (right < heapSize && arr[right] > arr[largest])
            largest = right;

        if (largest == i) break;

        swap(arr[i], arr[largest]);
        i = largest;
    }
}

此时:

复制代码
空间复杂度 = O(1)

七、常见误区总结

1. 把输入算进去

复制代码
vector<int> nums(n);

这个不算空间复杂度。


2. 忽略递归

复制代码
dfs(n)

必须考虑调用栈。


3. 忽略容器

复制代码
queue / stack / map

这些通常都是 O(n)。


八、通用判断模板(面试直接用)

判断空间复杂度可以按这个顺序:

  1. 有没有 new / vector / 数组 → 看是否 O(n)

  2. 有没有递归 → 看深度(n / log n)

  3. 有没有辅助结构(队列、栈、哈希表)

  4. 取最大的一项


九、一句话总结

空间复杂度本质就是:

复制代码
额外空间 + 递归调用栈

谁增长最快,就看谁。


相关推荐
JieE2127 小时前
LeetCode 101. 对称二叉树|JS 递归 + 迭代双解法,彻底搞懂镜像判断
javascript·算法
假如让我当三天老蒯20 小时前
前端跨域解决方案(学习用)
前端·javascript·面试
Colin草率地做慢慢地改20 小时前
关于QuickStore这个项目的重构(2)- 数据库建表文件
后端·面试·架构
JieE2121 天前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack202 天前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法
JustHappy2 天前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom2 天前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github
小小杨树2 天前
读懂色彩:拍照调色不再难
算法·计算机视觉·配色
假如让我当三天老蒯2 天前
模块化:ES Module 与 CommonJS 的区别
前端·面试
沉默王二2 天前
面试官:RAG 不用向量数据库,用 MySQL 硬扛?我:100 万向量不是很轻松?
mysql·面试·ai编程