前言
在程序开发中,开发者常常把注意力集中在“代码运行速度”上,而忽视了一个同样重要的方面——“内存消耗”。就像手机APP频繁卡顿甚至崩溃,往往并非因为算法执行得太慢,而是因为内存被悄无声息地耗尽。
空间复杂度就是用来衡量代码在运行过程中占用内存的标准,它帮助我们写出既高效又节省内存的代码。
一、什么是空间复杂度?
空间复杂度用于量化算法在运行时占用的临时内存大小,并用大O符号表示。它与时间复杂度是互补的——时间复杂度关注速度,空间复杂度关注内存使用。
经典对比:
// 写法1:空间复杂度 O(1)
int sum1(int[] arr) {
int total = 0; // 仅用1个变量
for (int num : arr) total += num;
return total;
}
// 写法2:空间复杂度 O(n)
int[] sum2(int[] arr) {
int[] res = new int[arr.length]; // 创建一个新数组
res[0] = 0;
for (int num : arr) res[0] += num;
return res; // 功能与sum1相同,但消耗了O(n)空间
}
sum1
只用了一个变量,空间复杂度是O(1)sum2
创建了一个长度为n
的新数组,空间复杂度是O(n)
→ 功能相同,但sum2
浪费了更多内存!
二、常见空间复杂度场景
1. O(1) 常量空间
内存消耗与输入规模无关。
// 案例:交换数组元素
void swap(int[] arr, int i, int j) {
int tmp = arr[i]; // 无论数组多大,只用1个变量
arr[i] = arr[j];
arr[j] = tmp;
}
2. O(n) 线性空间
内存与输入规模成线性关系。
// 案例:复制数组
int[] copyArray(int[] arr) {
int[] copy = new int[arr.length]; // 新数组占n个空间
for (int i=0; i<arr.length; i++) {
copy[i] = arr[i];
}
return copy; // 空间O(n)
}
3. O(n²) 平方空间
常见于二维数组或多层嵌套。
// 案例:生成n*n乘法表
int[][] createTable(int n) {
int[][] table = new int[n][n]; // 总空间n²
for (int i=0; i<n; i++) {
for (int j=0; j<n; j++) {
table[i][j] = (i+1)*(j+1);
}
}
return table; // 空间O(n²)
}
三、隐藏的内存陷阱
陷阱1:字符串拼接
// 错误写法:每次拼接生成新字符串
String s = "";
for (int i=0; i<10000; i++) {
s += i; // 实际空间O(n²),每次复制旧字符串
}
// 正确写法:使用StringBuilder
StringBuilder sb = new StringBuilder(); // 动态扩容,空间O(n)
for (int i=0; i<10000; i++) {
sb.append(i);
}
陷阱2:递归调用栈
// 递归计算阶乘:空间O(n)(调用栈深度n)
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n-1);
}
// 优化为迭代:空间O(1)
int factorialIter(int n) {
int res = 1;
for (int i=2; i<=n; i++) res *= i;
return res;
}
四、优化技巧
1. 原地操作(不额外占用空间)
// 原地反转数组(空间O(1))
void reverse(int[] arr) {
int i=0, j=arr.length-1;
while (i < j) {
int tmp = arr[i];
arr[i++] = arr[j];
arr[j--] = tmp;
}
}
2. 覆盖无用数据
// 斐波那契数列(空间O(1)替代递归O(n))
int fib(int n) {
if (n <= 1) return n;
int prev=0, curr=1;
for (int i=2; i<=n; i++) {
int next = prev + curr;
prev = curr; // 丢弃旧值
curr = next;
}
return curr;
}
3. 选择合适数据结构
- 基本类型(
int
)比包装类(Integer
)更节省内存。 - 频繁增删数据时,链表比数组更高效。
五、时间与空间的取舍
- 空间换时间:例如,使用哈希表缓存数据,快速查询。
- 时间换空间:例如,压缩存储数据,使用时解压。
结语
空间复杂度直接影响程序的效率和稳定性。通过警惕隐性内存消耗、优先原地操作,并在时间与空间之间做取舍,我们可以写出既高效又节省内存的代码。合理管理内存,就像整理行李箱一样,让程序既跑得快,又吃得少。