数据结构(02)— 时间复杂度与空间复杂度转换

1. 时间复杂度转化为空间复杂度

常用的降低时间复杂度的方法有递归、二分法、排序算法、动态规划等,降低空间复杂度的核心思路就是,能用低复杂度的数据结构能解决问题,就千万不要用高复杂度的数据结构。

在程序开发中,连接时间和空间的桥梁就是数据结构。对于一个开发任务,如果你能找到一种高效的数据组织方式,采用合理的数据结构的话,那就可以实现时间复杂度的再次降低。同样的,这通常会增加数据的存储量,也就是增加了空间复杂度。

转化步骤的流程如下:

  1. 暴力解法。在没有任何时间、空间约束下,完成代码任务的开发;
  2. 无效操作处理。将代码中的无效计算、无效存储剔除,降低时间或空间复杂度;
  3. 时空转换。设计合理数据结构,完成时间复杂度向空间复杂度的转移;

2. 转化步骤

2.1 暴力求解

假设有任意多张面额为 2 元、3 元、7 元的货币,现要用它们凑出 100 元,求总共有多少种可能性。

第一步:先使用暴力解法,不受时间、空间约束完成代码开发。

int count = 0;
for (int i = 0; i <= (100 / 7); i++) 
{
    for (int j = 0; j <= (100 / 3); j++) 
    {
        for (int k = 0; k <= (100 / 2); k++) 
        {
            if (i * 7 + j * 3 + k * 2 == 100) 
            {
                count += 1;
            }
        }
    }
}

在这段代码中,使用了 3 层的 for 循环。从结构上来看,是很显然的 O( n³ ) 的时间复杂度。然而,仔细观察就会发现,代码中最内层的 for 循环是多余的。因为,当你确定了要用 i 张 7 元和 j 张 3 元时,只需要判断用有限个 2 元能否凑出 100 - 7* i - 3* j 元就可以了。因此,代码改写如下:

2.2 剔除无效代码

第二步:将代码中的无效计算、无效存储剔除。

int count = 0;
for (int i = 0; i <= (100 / 7); i++) 
{
    for (int j = 0; j <= (100 / 3); j++) 
    {
        if ((100-i*7-j*3 >= 0)&&((100-i*7-j*3) % 2 == 0)) 
        {
            count += 1;
        }
    }
}

经过改造后,代码的结构由 3 层 for 循环,变成了 2 层 for 循环。很显然,时间复杂度就变成了O(n²) 。这样的代码改造,就是利用了方法论中的步骤二,将代码中的无效计算、无效存储剔除,降低时间或空间复杂度。

2.3 时空转化

查找出一个数组中,出现次数最多的那个元素的数值。例如,输入数组 a = [1,2,3,4,5,5,6 ] 中,查找出现次数最多的数值。从数组中可以看出,只有 5 出现了 2 次,其余都是 1 次。显然 5 出现的次数最多,则输出 5。

第一种方法:
采用两层的 for 循环完成计算。第一层循环,对数组每个元素遍历。第二层循环,则是对第一层遍历的数字,去遍历计算其出现的次数。

def func():
    a = [1, 2, 3, 4, 5, 5, 6]
    b = []
    for i in range(len(a)):
        count_num = 0
        for j in range(len(a)):
            if a[i] == a[j]:
                count_num += 1
        b.append(count_num)
    print("b is {}".format(b))
    max_value = max(b)
    print("max_value is {}".format(max_value))
    max_value_index = b.index(max_value)
    print("max_value_index is {}".format(max_value_index))
    appera_max_value = a[max_value_index]
    print("appera_max_value is {}".format(appera_max_value))

两层的 for 循环,很显然时间复杂度就是 O(n²)。而且代码中,几乎没有冗余的无效计算。如果还需要再去优化,就要考虑采用一些数据结构方面的手段,来把时间复杂度转移到空间复杂度了。

这个问题能否通过一次 for 循环就找到答案呢?一个直观的想法是,一次循环的过程中,我们同步记录下每个元素出现的次数。最后,再通过查找次数最大的元素,就得到了结果。具体而言,定义一个 k-v 结构的字典,用来存放元素-出现次数的 k-v 关系。那么首先通过一次循环,将数组转变为元素-出现次数的一个字典。接下来,再去遍历一遍这个字典,找到出现次数最多的那个元素,就能找到最后的结果了。

def func2():
    a = [1, 2, 3, 4, 5, 5, 6]
    d = {}
    for i in a:
        if i not in d:
            d[i] = 1
        else:
            d[i] += 1
    print("init d is {}".format(d))

    max_value = max(d.values())
    print("max_value is {}".format(max_value))
    for k, v in d.items():
        if v == max_value:
            print("appera_max_value is {}".format(k))

我们来计算下这种方法的时空复杂度。代码结构上,有两个 for 循环。不过,这两个循环不是嵌套关系,而是顺序执行关系。其中,第一个循环实现了数组转字典的过程,也就是 O(n) 的复杂度。第二个循环再次遍历字典找到出现次数最多的那个元素,也是一个 O(n) 的时间复杂度。

因此,总体的时间复杂度为 O(n) + O(n),就是 O(2n),根据复杂度与具体的常系数无关的原则,也就是O(n)的复杂度。空间方面,由于定义了 k-v 字典,其字典元素的个数取决于输入数组元素的个数。因此,空间复杂度增加为 O(n)

这段代码的开发,就是借鉴了方法论中的步骤三,通过采用更复杂、高效的数据结构,完成了时空转移,提高了空间复杂度,让时间复杂度再次降低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wohu007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值