給定一個由正整數組成且不存在重複數字的數組,找出和爲給定目標正整數的組合個數。
示例:
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尋找是否存在,這個會使程序減慢