240
生活,简单就好!

Python编程题39--所有奇数长度子列表的和

题目

给定一个非空的正整数列表 arr ,请计算所有可能的奇数长度子列表的和(子列表指原列表中的一个连续子序列)。

例如:

给定一个列表:[1, 4, 2, 5, 3],返回结果:58

解释:所有奇数长度子列表及它们的和为:
[1] = 1
[4] = 4
[2] = 2
[5] = 5
[3] = 3
[1,4,2] = 7
[4,2,5] = 11
[2,5,3] = 10
[1,4,2,5,3] = 15

我们将所有值求和得到 1 + 4 + 2 + 5 + 3 + 7 + 11 + 10 + 15 = 58

实现思路1

  • 直接进行暴力破解,需要3层循环,时间复杂度为O(n^3)
  • 第一层循环,遍历出所有符合条件的子列表长度 small_arr_len
  • 第二层循环,遍历出原列表的所有元素 arr[i]
  • 第三层循环,对当前找到的奇数子列表,即 arr[i:i+small_arr_len] 中的所有元素进行求和

代码实现1

def sumOddLengthSubarrays(arr):
    res_sum, arr_len = 0, len(arr)
    for small_arr_len in range(1, arr_len + 1, 2):  # 遍历所有可能符合条件的子列表长度
        for i in range(arr_len):  # 遍历原列表的所有元素
            if i + small_arr_len <= arr_len:  # 判断是否越界
                for j in range(i, i + small_arr_len):  # 对当前奇数子列表求和
                    res_sum += arr[j]
    return res_sum

上面的方法中,可以看到第三层循环时,每次都需要对 i 到 i + small_arr_len - 1 下标的元素进行重复的求和计算,如果我们能找到一种方法,实现快速计算出两个下标之间的所有元素的和,那么就可以对上面方法实现优化。

在这里,我们可以利用 前缀和 来实现,比如针对列表 arr = [1, 4, 2, 5, 3],我们可以得到从第一个位置到当前位置的所有元素之和,也就是一个前缀和的列表 pre_sum_list = [1, 5, 7, 12, 15],当我们需要计算下标 2 到下标4 的子列表 [2, 5, 3] 所有元素之和,就可以直接通过计算 pre_sum_list[4] - pre_sum_list[2 - 1] = 15 - 5,得到结果 10

实现思路2

  • 使用前缀和列表,时间复杂度为O(n^2)
  • 最开始先使用一个循环,计算并得到一个包含有前缀和的列表 pre_sum_list
  • 接着再计算所有奇数长度子列表的和,第一层循环,遍历出所有符合条件的子列表长度 small_arr_len
  • 第二层循环,遍历出原列表的所有元素,下表为 i
  • 第三层循环,对当前找到的奇数子列表 arr[i:i+small_arr_len] ,借助 pre_sum_list 求出所有元素之和,如果当前位置为 0 ,那么当前奇数子列表之和为 pre_sum_list[i + small_arr_len - 1],否则为 pre_sum_list[i + small_arr_len - 1] - pre_sum_list[i - 1]

代码实现2

def sumOddLengthSubarrays(arr):
    res_sum, arr_len = 0, len(arr)
    pre_sum_list = [0] * arr_len
    for i in range(arr_len):  # 获取前缀和列表
        pre_sum_list[i] = pre_sum_list[i - 1] + arr[i] if i != 0 else arr[i]
    for small_arr_len in range(1, arr_len + 1, 2):  # 遍历所有可能符合的子列表长度
        for i in range(arr_len):  # 遍历原列表
            if i + small_arr_len <= arr_len:   # 判断是否越界
                if i == 0:
                    res_sum += pre_sum_list[i + small_arr_len - 1]
                else:
                    res_sum += pre_sum_list[i + small_arr_len - 1] - pre_sum_list[i - 1]
    return res_sum

上面方法的时间复杂度为O(n^2),那么有没有更优的解决办法呢?

我们可以直接利用 数学方式 来求和,本题目的是为了计算出所有奇数长度子列表的和,换个角度思考下,我们只需要求出原列表中每个元素在所有奇数长度子列表中出现的次数即可,这样一来,我们再对每个元素按其出现次数求和,最后再相加就能得到最终结果了。

针对列表 arr = [1, 4, 2, 5, 3] 中的元素 4,其左侧元素个数为 1 ,右侧元素个数为 3,包含元素 4 的所有奇数长度子列表分为以下两种情况:

第一种情况,因为 奇数个 + 1 + 奇数个 = 奇数个,所以可从元素4 左右两侧均取奇数个,从左侧取奇数个的方法有 1 种(取1个),从右侧取奇数个的方法有 2 种(取1个、取3个),组合起来总的方法就有 1 * 2 = 2种:[1, 4, 2]、[1, 4, 2, 5, 3]

第二种情况,因为 偶数个 + 1 + 偶数个 = 奇数个,所以可从元素4 左右两侧均取偶数个,从左侧取偶数个的方法有 1 种(取 0 个),从右侧取奇数个的方法有 2 种(取0个、取2个),组合起来总的方法就有 1 * 2 = 2种:[4]、[4, 2, 5]

接着把上面两种情况相加 2 + 2 = 4,该结果表示所有奇数长度子列表中,包含有元素4的情况一共有4种,也就是说元素4在 所有奇数长度子列表 中的出现次数为4,所以此时可以求出所有奇数长度子列表中的元素4之和:4 * 4 = 16

最后,分别求出每个元素在 所有奇数长度子列表 中的出现次数并依次求和,就可以得到最终结果。

实现思路3

  • 使用数学方式求和,只需循环一次,时间复杂度为O(n)
  • 遍历过程中,首选分别求出当前元素左侧及右侧的元素个数 count_left,、count_right
  • 从当前元素左右侧只取奇数个元素,分别求出从当前元素左侧及右侧取奇数个元素的可能性 left_odd、right_odd
  • 从当前元素左右侧只取偶数个元素,分别求出从当前元素左侧及右侧取偶数个元素的可能性 left_even、right_even
  • 接着分别求出取奇数个和偶数个的所有组合的可能性 odd_all、even_all
  • 最后得到当前元素在 所有奇数长度子列表 中的出现次数为 odd_all + even_all ,并可对当前元素进行最终求和

代码实现3

def sumOddLengthSubarrays(arr):
    res_sum, arr_len = 0, len(arr)
    for i in range(arr_len):
        # count_left 表示当前元素左侧的元素个数,count_left 表示当前元素右侧的元素个数
        count_left, count_right = i, arr_len - i - 1
        # 奇数 + 1 + 奇数 = 奇数, left_odd 表示从当前元素左侧取奇数个元素, right_odd 表示从当前元素右侧取奇数个元素
        left_odd, right_odd = (count_left + 1) // 2, (count_right + 1) // 2
        # 偶数 + 1 + 偶数 = 奇数, left_even 表示从当前元素左侧取偶数个元素, right_even 表示从当前元素右侧取偶数个元素
        left_even, right_even = count_left // 2 + 1, count_right // 2 + 1
        # odd_all 表示从两侧均取奇数个元素的所有可能性, even_all 表示从两侧均取偶数个元素的所有可能性
        odd_all, even_all = left_odd * right_odd, left_even * right_even
        # 所有子列表中统计出当前元素的个数 odd_all + even_all ,对当前元素求和即可
        res_sum += arr[i] * (odd_all + even_all)
    return res_sum

更多Python编程题,等你来挑战:Python编程题汇总(持续更新中……)

posted @ 2021-12-25 19:40  wintest  阅读(210)  评论(0编辑  收藏  举报