组合求和问题
目录
这两天接连遇到这种寻找组合求和的问题,感觉有必要研究记录一下。
问题很简单,对于给定的一个整数数组 nums
和一个目标值 target
,我们需要从中选出一些数,使得它们的和为 target
,每个数只能使用一次。
这个问题我一开始看到的时候直接发懵,不知道该如何下手,也不清楚该如何定义问题边界,如何使用递归或者递推求解。
最后看到寥寥几行代码,陷入呆滞,这我还真想不到……
def find_sum(nums, res):
dp = [False] * (res + 1)
dp[0] = True
for num in nums:
for i in range(res, num-1, -1):
dp[i] = dp[i] or dp[i-num]
return dp[res]
传入数组和目标值,该函数将返回能否组成目标值。
该算法使用一个数组dp
记录0到目标值的状态,基于动态规划的思想,逐步更新状态。算法逐个处理nums
中的数num
,依照现有dp
状态再加上num
更新dp
。值得注意的是,dp
更新需要考虑原状态及和num
的组合情况。且应当倒序更新dp
,以避免在同一轮中使用num
多次。
实际上,这里还可以通过记录dp
为True
的索引来进一步优化,避免无必要的状态更新判断。
def find_sum(nums, res):
stash = set()
for num in nums:
tmp = set()
for state in stash:
tmp.add(state+num)
stash.update(tmp)
stash.add(num)
return res in stash
如果我们想知道具体是哪些数能组成目标值,就没必要做这种优化了,继续沿用dp
的方案更容易回溯得到结果。
这里,我们使用pre
数组记录下该组合数,仅在dp
状态首次通过num
改变为True
时记录。最后通过目标值回溯pre
数组,得到组成目标值的所有数。
def find_sum(nums, res):
pre = [-1] * (res+1)
dp = [False] * (res+1)
dp[0] = True
for num in nums:
for i in range(res, num-1, -1):
if not dp[i] and dp[i-num]:
dp[i] = True
pre[i] = num
group = []
if dp[res]:
idx = res
while pre[idx] > 0:
group.append(pre[idx])
idx = idx - pre[idx]
return True, group
else:
return False, []