如何理解时间复杂度与空间复杂度?| 面试刷题笔记02

在刷 LeetCode 时,我们不仅要写出正确的代码,还需要分析算法的时间复杂度和空间复杂度。

时间复杂度衡量算法的执行工作量如何随输入规模增长,空间复杂度衡量算法所需的额外空间如何随输入规模增长。

本文以 LeetCode 128「最长连续序列」为例,学习如何分析这两种复杂度,以及一个容易误解的知识点:循环嵌套不一定意味着时间复杂度是 O(n²)。

一、先看我们的解法

dart 复制代码
class Solution {
    public int longestConsecutive(int[] nums) {
        HashSet<Integer> set = new HashSet<>();

        // 去重,方便后续查找
        for (int num : nums) {
            set.add(num);
        }

        int maxLength = 0;

        // 从连续序列的起点开始查找
        for (int num : set) {
            if (!set.contains(num - 1)) {
                int endNum = num;

                while (set.contains(endNum + 1)) {
                    endNum++;
                }

                int newLength = endNum - num + 1;
                maxLength = Math.max(maxLength, newLength);
            }
        }

        return maxLength;
    }
}

设输入数组的长度为 (n),去重后 HashSet 中的元素数量为 (m),显然 (m \le n)。

在 Java HashSet 正常哈希性能的假设下,这份代码的复杂度为:

  • 时间复杂度:平均 O(n)
  • 额外空间复杂度:O(n)

下面分析为什么。

二、什么是时间复杂度?怎么判断?

时间复杂度关注的是:当输入规模不断增大时,算法的执行工作量如何增长。

它并不是计算代码运行了多少秒,而是分析执行操作的增长趋势。

例如:

  • O(1):执行工作量基本不随输入规模增长。
  • O(n):工作量与输入规模成正比。
  • O(n²):工作量通常与输入规模的平方成正比。

1. 分析第一段循环

dart 复制代码
for (int num : nums) {
    set.add(num);
}

假设数组有 100 个元素,这个循环执行 100 次;如果有 10000 个元素,就执行 10000 次。

因此,遍历次数与 (n) 成正比。

HashSet 的 add() 在平均情况下是 O(1),所以这段代码的平均时间复杂度是:

O(n \\times 1)=O(n)

2. 分析第二段循环

dart 复制代码
for (int num : set) {
    if (!set.contains(num - 1)) {
        // ...
    }
}

HashSet 最多包含 (m) 个不同的元素,因此外层循环执行 (m) 次。

每次 contains() 平均为 O(1),所以单看起点判断,时间复杂度为 O(m)。

但真正需要注意的是里面的 while。

3. 为什么循环嵌套不一定是 O(n²)?

很多人看到 for 里面还有 while,就会直接判断为 O(n²)。

这是一个常见误区。

判断复杂度不能只看循环写了几层,还要看内层循环累计执行了多少次。

我们的代码是:

dart 复制代码
if (!set.contains(num - 1)) {
    int endNum = num;

    while (set.contains(endNum + 1)) {
        endNum++;
    }
}

关键在于起点判断:

dart 复制代码
!set.contains(num - 1)

只有当前数字的前一个数字不存在时,才会进入 while。

例如,Set 中有:

复制代码
{1, 2, 3, 4, 100, 101, 102}

当遍历到 1 时,它是连续序列的起点,while 会依次找到 2、3、4。

当遍历到 2、3、4 时,由于它们的前一个数字都存在,不会再次扫描这个连续序列。

同理,100 会负责扫描 101、102,而 101、102 不会重复扫描。

因此,每个连续序列只会从起点开始扫描一次。所有 while 循环累计处理的元素数量为 O(m),而不是每次外层循环都重新扫描整个集合。

整体时间复杂度为:

T(n)=O(n)+O(m)+O(m)

由于 (m < n),最终得到:

{T(n)=O(n)}

这里需要注意:如果没有起点判断,让每个数字都执行向后扫描,那么对于连续的 (n) 个数字,累计工作量可能达到 O(n²)。

所以,循环嵌套不一定是 O(n²)。真正决定复杂度的是所有操作的累计工作量。

三、什么是空间复杂度?怎么判断?

空间复杂度关注的是:随着输入规模增长,算法需要多少额外空间。

分析时,通常不把题目传入的原数组本身算作额外空间。

回到我们的代码:

ini 复制代码
HashSet<Integer> set = new HashSet<>();

这个 Set 最多保存 (n) 个不同的数字。

如果数组中的数字全部不同,Set 就需要存储 (n) 个元素,因此需要 O(n) 的额外空间。

其他变量,例如:

ini 复制代码
int maxLength = 0;
int endNum = num;
int newLength = endNum - num + 1;

它们都只占用固定数量的空间,属于 O(1)。

因此:

S(n)=O(n)+O(1)=O(n)

最终,算法的额外空间复杂度为 O(n)。

一个容易混淆的地方

循环次数多,不代表空间复杂度就高。

例如:

ini 复制代码
int sum = 0;

for (int num : nums) {
    sum += num;
}

这个循环执行 (n) 次,时间复杂度是 O(n),但它只使用固定数量的变量,额外空间复杂度仍然是 O(1)。

反过来,即使只遍历一次数组,如果在循环中将所有元素保存到新的集合中,空间复杂度仍然可能是 O(n)。

时间看执行了多少工作,空间看额外存储了多少数据。

四、判断复杂度时,还要注意什么?

1. 多段代码的复杂度通常相加

例如:

scss 复制代码
for (...) {
    // O(n)
}

for (...) {
    // O(n)
}

两段循环的总工作量为:

O(n)+O(n)=O(2n)=O(n)

大 O 表示法忽略常数倍,因此不是 O(2n)。

2. 连续嵌套循环通常需要相乘,但不能机械判断

例如:

ini 复制代码
for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        // 固定时间操作
    }
}

内层循环对每次外层循环都执行 (n) 次,总操作次数为 (n \times n),因此时间复杂度是 O(n²)。

但如果内层循环的总工作量受到条件限制,或者多个循环共同处理同一批元素,就需要进一步分析,不能仅凭代码缩进层数判断。

3. HashSet 的操作通常是平均 O(1)

本题使用 HashSet,依赖 add() 和 contains() 的平均常数时间性能。

如果发生严重哈希冲突,单次操作可能变慢。因此,通常将本题的时间复杂度表述为平均 O(n),而不是声称任何情况下都绝对是 O(n)。

五、总结

分析算法复杂度时,可以记住以下思路:

  1. 时间复杂度: 统计操作随着输入规模增长的趋势。
  2. 空间复杂度: 找出额外创建的数据结构,判断它们最多保存多少数据。
  3. 循环嵌套: 不要只看层数,要分析内层循环的累计工作量。
  4. 简化表达式: 忽略常数倍和低阶项,例如 O(2n + 1) 简化为 O(n)。

以 LeetCode 128 为例,第一段循环构建 HashSet,耗时 O(n);第二段循环虽然包含 while,但起点判断避免了重复扫描,所有扫描工作累计为 O(n)。同时,HashSet 最多保存 (n) 个元素,所以额外空间为 O(n)。

最终结论是:

  • 时间复杂度:平均 O(n)
  • 额外空间复杂度:O(n)

理解复杂度的关键,不是数循环有几层,而是弄清楚:算法总共做了多少工作,以及额外使用了多少空间。

相关推荐
姜鱼问生1 小时前
服务器磁盘满了?从告警到清理的完整排查
后端·算法·架构
suhan1 小时前
【数据结构】三、栈和队列:4.栈的应用(括号匹配,四则运算表达式求值,进制转换,递归)
数据结构·后端·算法
suhan1 小时前
【数据结构】三、栈和队列:2.顺序栈&共享栈(顺序栈的初始化,判空,进栈,出栈,读取栈顶,顺序栈实例)
数据结构·后端·算法
iCxhust1 小时前
Matlab求解发电机的极限切除角
开发语言·算法·matlab·电力系统
AI语宙漫游指南1 小时前
RAG 向量检索(上):从 FLAT 到 IVF,查得快为何可能漏?
后端·算法
垆边人似月.2 小时前
华为题:敏感词替换
算法
0+1113 小时前
算法 --哈希表
数据结构·算法·散列表
Interview Aid1123 小时前
Stripe VO 面经汇总:5 轮面试题目与考察重点
算法
EEET智选3 小时前
科技晨间速报 | AI 安全警钟、开源新模型与苹果智能家居
人工智能·科技·算法·机器学习·ai