1、时间复杂度
1.1 什么是时间复杂度
简单说,它是用来衡量一个算法「执行步骤」随着数据量增大时的增长趋势。比如:
- 煮一锅水需要5分钟(无论水量多少)→ 时间复杂度O(1)
- 检查100个学生是否到齐需要逐个点名 → 时间复杂度O(n)
- 全班同学两两握手需要100*99次 → 时间复杂度O(n²)
1.2 如何计算?
三步走:
-
找最耗时的部分(通常是循环)
▪️ 单层循环:执行n次 → O(n)
▪️ 双层嵌套循环:n*n次 → O(n²) -
忽略次要因素
▪️ 计算表达式(如3n²+5n+10)→ 只保留最高阶项n²
▪️ 去掉系数(如2n² → 简化为n²) -
常见类型对比
▪️ O(1):固定步骤(如数组取元素)
▪️ O(n):线性增长(如遍历数组)
▪️ O(log n):每次砍半(如二分查找)
▪️ O(n²):平方增长(如冒泡排序的最坏情况)
1.3 实际案例
假设代码:
for i in range(n): # 执行n次
print("步骤1") # 执行n次
for j in range(n): # 执行n*n次
print("步骤2") # 执行n*n次
总步骤 = n + n + n² + n² = 2n² + 2n → 简化为O(n²)
循环套几层,复杂度就指数级增长;数据量越大,高阶项的影响越明显。
关于空间复杂度的概念和计算方法,我用最通俗的方式解释如下:
2、空间复杂度
2.1 什么是空间复杂度?
简单说,它是用来衡量一个算法运行过程中额外占用
内存空间随着数据量增大的增长趋势。
例如:
-
煮一锅水只需要一个锅(无论水量多少)→ 空间复杂度 O(1)
-
给全班同学每人发一本作业本 → 需要 n 本作业本的空间 → 空间复杂度 O(n)
-
递归计算阶乘时,每层递归都要存一个临时值 → 递归深度为 n → 空间复杂度 O(n)
注意:
- 这里说的“额外空间”不包括输入数据本身占用的空间(比如输入一个数组本身不算额外空间)。
- 空间复杂度关注的是算法运行过程中临时开辟的内存(比如新建的变量、数组、递归调用栈等)。
2.2 如何计算?
三步法:
- 找变量
- 关注算法中显式创建的变量或数据结构:
- 固定变量(如单个数字、字符串)→ 空间复杂度 O(1)
- 动态分配的空间(如长度为 n 的数组、递归调用栈深度为 n)→ 空间复杂度 O(n)
- 看增长关系
- 分析变量大小是否随输入数据量(n)变化:
- 例1:循环中创建了一个固定大小的临时数组 → O(1)
- 例2:循环中创建了一个长度为 n 的数组 → O(n)
- 例3:递归调用深度为 log n → O(log n)
- 简化规则(和大O时间复杂度类似)
- 忽略常数项:如 O(2n+100) → 简化为 O(n)
- 保留最高阶项:如 O(n² + 3n) → 简化为 O(n²)
- 所有常数空间 → O(1)
2.3 常见类型对比
类型 | 例子 | 典型场景 |
---|---|---|
O(1) | 固定变量(如计数器) | 简单循环、变量交换 |
O(n) | 动态数组、递归调用栈 | 遍历生成数组、单层递归 |
O(n²) | 二维数组、嵌套数据集合 | 矩阵运算、动态规划表格 |
O(log n) | 分治算法的递归调用栈 | 二分查找、快速排序递归深度 |
2.4 实际案例
案例1:O(1) 空间
def sum_numbers(n):
total = 0 # 固定变量 → O(1)
for i in range(n): # 循环变量i → O(1)
total += i
return total
分析:无论 n 多大,只用到了 total
和 i
两个固定变量 → 空间复杂度 O(1)
案例2:O(n) 空间
def create_array(n):
arr = [] # 动态数组 → O(n)
for i in range(n):
arr.append(i) # 数组长度随n增长
return arr
分析:创建了一个长度为 n 的数组 → 空间复杂度 O(n)
总结
- 口诀:变量随
数据量
变,复杂度就跟着变;递归层数分情况,临时数组看长度。 - 关键区别:时间看步骤增长,空间看内存占用增长。
- 优化思路:优先降低时间复杂度,空间不足时再优化空间复杂度(如用变量覆盖代替新建数组)。