题目
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
提示:
- 1 <= candidates.length <= 100
- 1 <= candidates[i] <= 50
- 1 <= target <= 30
答案
我们可以先对数组进行排序,方便剪枝以及跳过重复的数字。
接下来,我们设计一个函数 dfs(i,s),表示从下标 i 开始搜索,且剩余目标值为 s,其中 i 和 s 都是非负整数,当前搜索路径为 t,答案为 ans。
在函数 dfs(i,s) 中,我们先判断 s 是否为 0,如果是,则将当前搜索路径 t 加入答案 ans 中,然后返回。如果 i≥n,或者 s<candidates[i],说明当前路径不合法,直接返回。否则,我们从下标 i 开始搜索,搜索的下标范围是 j∈[i,n),其中 n 为数组 candidates 的长度。在搜索的过程中,如果 j>i 并且
candidates[j]=candidates[j−1],说明当前数字与上一个数字相同,我们可以跳过当前数字,因为上一个数字已经搜索过了。否则,我们将当前数字加入搜索路径 t 中,然后递归调用函数 dfs(j+1,s−candidates[j]),然后将当前数字从搜索路径 t 中移除。
我们也可以将函数 dfs(i,s) 的实现逻辑改为另一种写法。如果我们选择当前数字,那么我们将当前数字加入搜索路径 t 中,然后递归调用函数 dfs(i+1,s−candidates[i]),然后将当前数字从搜索路径
t 中移除。如果我们不选择当前数字,那么我们可以跳过与当前数字相同的所有数字,然后递归调用函数 dfs(j,s),其中 j 为第一个与当前数字不同的数字的下标。
在主函数中,我们只要调用函数 dfs(0,target),即可得到答案。
时间复杂度 O(2 n ×n),空间复杂度 O(n)。其中 n 为数组 candidates 的长度。由于剪枝,实际的时间复杂度要远小于
O(2 n ×n)。
class Solution(object):
def combinationSum2(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
def dfs(i, s):
if s == 0:
ans.append(t[:])
return
if i >= len(candidates) or s < candidates[i]:
return
for j in range(i, len(candidates)):
if j > i and candidates[j] == candidates[j - 1]:
continue
t.append(candidates[j])
dfs(j + 1, s - candidates[j])
t.pop()
candidates.sort()
ans = []
t = []
dfs(0, target)
return ans
class Solution(object):
def combinationSum2(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
def dfs(i, s):
if s == 0:
ans.append(t[:])
return
if i >= len(candidates) or s < candidates[i]:
return
x = candidates[i]
t.append(x)
dfs(i + 1, s - x)
t.pop()
while i < len(candidates) and candidates[i] == x:
i += 1
dfs(i, s)
candidates.sort()
ans = []
t = []
dfs(0, target)
return ans