377. 組合總和IV

給定一個由正整數組成且不存在重複數字的數組,找出和爲給定目標正整數的組合個數。

示例:

nums = [1, 2, 3]
target = 4

所有可能的組合爲:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)

請注意,順序不同的序列被視作不同的組合。

因此輸出爲 7。

思路: 看到這個題目,有兩種思路,一個是動態規劃,一個遞歸。這裏只需要求出組合個數,不需要羅列出所有的組合,所以動態規劃即可。

有幾個需要注意的地方

  • 題目中 不存在重複的數字 有什麼用途?
  • 如何控制順序不同的序列 ?
  • 怎麼寫狀態轉移方程?

先思考一下 狀態轉移方程:
我們直接把題目要求的當做我們的狀態dp[i]: 和爲i的組合個數。

  • 第一個數選擇 nums[0], 那麼有dp[i - nums[0]]個組合個數
  • 第一個數選擇 nums[1], 那麼有dp[i - nums[1]]個組合個數
  • 第一個數選擇 nums[2], 那麼有dp[i - nums[2]]個組合個數

因此對於dp[i], 枚舉所有nums中的數num作爲第一個數,則有

dp[i] = sum(dp[i - num]), num in nums, 而且 num <= i

初始條件: dp[0] = 1, 即和爲0的組合個數,有1個: 空。

這樣構造的狀態轉移方程的屬性滿足了"順序不同的序列"的要求。

至於不存在重複的數字,因爲每個數字可以任意多次的選擇,所以這個問題不重要。

程序如下:

class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        dp = [0] * (target + 1)
        dp[0] = 1
        for i in range(1, target + 1):
            for num in nums:
                if num <= i:
                    dp[i] += dp[i - num]
        return dp[target]

最初寫了如下的程序:

# 超時, 通過 16 / 17 個測試用例
class Solution:
    def combinationSum4(self, nums: List[int], target: int) -> int:
        dp = [0] * (target + 1)
        dp[0] = 1
        for i in range(1, target + 1):
            for j in range(1, i+1):
                if j in nums:
                    dp[i] += dp[i - j]
        return dp[target]

這樣寫有兩個壞處:

  • 需要遍歷每一個1 - i之間的數,有些情況會比nums多遍歷很多次
  • 每次遍歷 1 - i之間的數,都需要在nums尋找是否存在,這個會使程序減慢
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章