算法界的“时间旅行者”与“空间大师”:揭秘时间复杂度与空间复杂度

225 阅读1分钟

最近一直在刷力扣上面的算法题,大部分题目回答后都有与其他人的时间复杂度和空间复杂度的对比,虽然题目写出来了,但是超越%5的人是真的揪心吧。学过,但是记得不是很清了,就记得 时间复杂度O(n)空间复杂度S(n),还有代码的执行次数T(n) 了,所以来巩固一下时间复杂度和空间复杂度的计算。

时间复杂度

时间复杂度是对算法运行时间的一种度量方式,它描述了算法执行时间与输入数据规模之间的关系。一般用大O来描述。

常见的几种时间复杂度及其比较包括但不限于:

O(11)<O(log2nlog_2n)<O(nn)<O(nlog2nnlog_2n)< O(n2n^2)<O(n3n^3)< ... <O(2n2^n)<O(n!n!)

时间复杂度的计算方法

时间复杂度的计算基于代码的执行次数T(n)。随着输入规模n的增大,我们关注的是算法执行次数的增长趋势,而不是具体的执行次数。因此,在计算时间复杂度时,我们可以忽略较低阶的项和高阶项前的系数,只保留最高次项来表示时间复杂度。

例如,如果一个算法的执行次数为T(n) = 3n^2 + 2n + 1,那么其时间复杂度就是O(n^2),因为当n趋近于无穷时,n^2的增长速度远远超过n和常数项,故可以忽略n与常数项对于增长的影响。

单层循环的计算:

我们可以分为以下步骤来解决:

  1. 列出循环趟数t及每轮循环值x的变化值。
  2. 找到t与x的关系。
  3. 确定循环停止条件。
  4. 联立两式解方程。

eg.1:

int x = 0;
while(n >= (x + 1) * (x + 1))
    x++;

根据步骤可以分为:

  1. 列出循环趟数t以及循环值x的变化:
t012...n
x012...n
  1. 找到t与x的关系:t=xt = x
  2. 确定循环停止条件:n=(x+1)2n = (x + 1)^2
  3. 联立解得:t=n1t = \sqrt{n} - 1

所以时间复杂度为:T=O(n)T = O(\sqrt{n})

二层循环的计算:

我们可以分为以下步骤来解决:

  1. 列出外层循环中t的变化值与内层语句中x的执行次数。
  2. 求和,写结果。

eg.2:

int x = 0;
for(int i = 0; i < n; i++){
   for(int j = 0; j < 2*i; j++){
      x++;
   }
}

根据步骤可以分为:

  1. 列出外层循环趟数t以及内层循环值x的变化:
t012...n
x024...2n
  1. 求和:x=n(2+2n)2x = \dfrac{n(2+2n)}{2} = n(n+1)n(n+1)= n2n^2

所以时间复杂度为:T=O(n2)T = O(n^2)

多层循环的计算:

抽象为三维立体图计算体积

eg.3:

int x = 0;
for(int i = 0; i < n; i++){
   for(int j = 0; j < i; j++){
      for(int k = 0; k < j; k++){
          x++;
      }
   }
}

image.png

空间复杂度

空间复杂度是衡量一个算法执行过程中所占用的额外存储空间大小。这里的“额外”指的是除了输入数据本身占用的空间之外,算法为了完成任务还需要多少额外的存储空间。同样采用大O符号来表示。

eg.1:

function swap(a, b) {
    let t = a; // 需要额外的一个变量空间
    a = b;
    b = t;
    return [a, b];
}//函数功能为交换a,b值

在这个例子中,无论输入的数字有多大,都只需要一个额外的变量来存储临时值。

因此,这个算法的空间复杂度为O(1)O(1),表示所需的空间不会随着输入数据规模的变化而变化。

eg.2:

function foo(nums) {
    const r = new Array(nums.length); // 额外创建了一个与输入数组相同长度的新数组
    for (let i = 0; i < nums.length; i++) {
        r[i] = nums[i] * nums[i];
    }
    return r;
}//接收整数数组作为输入,将原数组对应位置元素平方后返还

在这个例子中,算法需要创建一个与输入数组长度相同的数组来存储结果。、

因此,随着输入数组长度的增加,所需额外空间也会线性增加。所以,这个算法的空间复杂度为 O(n)O(n)

eg.3:

function foo(n) {
    if (n === 0) {
        return 1;
    }
    return n * foo(n - 1);
}//求n的阶乘

在这个递归实现的阶乘函数中,每次调用foo函数都会增加一次调用栈的深度。

因此,如果n很大,调用栈可能会变得非常深,占用大量的内存空间。这个算法的空间复杂度为O(n)O(n),因为最坏情况下,调用栈的深度与输入n成正比。

小结

总之,理解并掌握时间复杂度和空间复杂度的概念对于提高算法效率至关重要。在实际编程中,合理选择数据结构和优化算法可以显著提升程序性能。

---欢迎各位点赞、收藏、关注,如果觉得有收获或者需要改进的地方,希望评论在下方,不定期更新

0bae-hcffhsw0416753.gif