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編程題彙總(持續更新中……)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章