时间复杂度和空间复杂度是评价算法的两个重要依据,下面我们来做个简单介绍。
# 时间复杂度
算法的时间复杂度描述的是随着输入规模的增大,算法对应的执行总次数的一个变化趋势,在实际开发中并不需要去逐行计算。
我们来看一行最简单的代码:
console.log('hello world')
这行代码在程序运行的时候只执行了一次:
T(n) = 1
咱们再来看个稍微复杂点的代码:
const arr = [1, 2]
for (let i = 0; i < arr.length; i++) {
console.log(arr[i])
}
在程序运行后 const arr = [1, 2] 这行代码只执行了一次。
下面来拆分 for 循环中的代码看看 let i= 0 这行代码执行了一次, i < arr.length 执行了 n + 1 次, i++ 这行执行了 n 次,console.log(arr[i]) 这行代码执行了 n 次。所以上面的代码总共执行了:
T(n) = 1 + 1 + n + 1 + n + n => 3n + 3
再来看一个复杂点的:
const arr = [1, 2, 3]
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
console.log(arr[j])
}
}
用上面的方法分析这段代码执行了多少次:
// 1
const arr = [1, 2, 3]
// 1 n+1 n
for (let i = 0; i < arr.length; i++) {
// n n*(n+1) n*n
for (let j = 0; j < arr.length; j++) {
// n*n
console.log(arr[j])
}
}
得:
T(n) = 1 + 1 + n + 1 + n + n + n * (n + 1) + n * n => 3n^2 + 4n + 3
简化规则:
- 若 T(n) 是常数,那么无脑简化为1
- 若 T(n) 是多项式,比如 3n^2 + 4n + 3,我们只保留次数最高那一项,并且将其常数系数无脑改为1。
通过简化,以上例子的时间复杂度分别为:
T(n) = 1 => O(n) = 1
T(n) = 3n + 3 => O(n) = n
T(n) = 3n^2 + 4n + 3 => O(n) = n^2
再来看一个例子:
const arr = [1, 2, 3]
for (let i = 1; i < arr.length; i = i * 2 ) {
console.log(arr[i])
}
可以看到上面的代码和之前的的代码有个特殊的地方,就是 i = i * 2 ,之前的代码是 i++ 因为这个条件的变化,对于代码的时间复杂度分析就难了些。
前面的代码的 n 可以暂时理解为数组的长度,这里我们依然把 n 理解为数组的长度,跳出 for 循环的条件 i < n 可以理解成 i >= n,换而言之可以理解成 i 按照 i = i * 2 的规则递增 x 次后 i>=n 依然成立,举例如下:
i = 1 => i * 2 => 2
i = 2 => i * 2 => 4
i = 4 => i * 2 => 8
i = 8 => i * 2 => 16
i = 16 => i * 2 => 32
...
从上面可以看出来递增 x 后,i 的值为 2^x 。也就是我们需要解的式子如下:
2^x >= n => n = log2n
底数和系数都可以被省略,得
O(n) = logn
所以上面代码的时间复杂度为 logn 。
常见的时间复杂度有以下几种:

# 空间复杂度
空间复杂度用来描述在程序运行期间内存的增长趋势。
常见的空间复杂度有:O(1)、O(n) 和 O(n^2)
const arr = [1, 2, 3]
for (var i = 0, len = arr.length; i < len; i++) {
console.log(arr[i])
}
上面的代码总共有三个变量:
arr
i
len
随着代码的运行,内存空间并不会有什么增加,所以对应的空间复杂度为 O(1) 。
let n = 10 // n 的值可变
const arr = []
for (var i = 0; i < n; i++) {
arr[i] = i
}
上述代码中的变量有:
n
arr
i
上述代码由于数组 arr 的大小由 n 的大小决定,随着 n 的变化线性变化,所以上述代码的空间复杂度为 O(n) 。
由于现在的设备性能越来越好,内存也越来越大,一般情况下我们更多的会考虑一个算法的时间复杂度,很多时候我们会用空间换时间。