39. 组合总和
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
def back_tracking(result, subset, candidates, target, _sum, start_index):
if _sum == target:
result.append(subset.copy())
return
if len(subset) > 150 or _sum > target:
return
for i in range(start_index,len(candidates)):
_sum += candidates[i]
subset.append(candidates[i])
back_tracking(result, subset, candidates, target, _sum, i)
_sum -= candidates[i]
subset.pop()
result = []
subset = []
_sum = 0
back_tracking(result, subset, candidates, target, _sum, 0)
return result
因为可以选择自身,所以start_index就是为i
题目的边界条件是不让子集长度大于150个,所以设置一个return 条件 len(subset) > 150或者_sum已经大于target了
40. 组合总和Ⅱ
一开始考虑的是排序之后,如果subset在result中了,就不加入。但是发现超时了,还是需要剪枝才可以
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
def back_tracking(result, subset, candidates, target, _sum, start_index):
if _sum == target and subset not in result:
result.append(subset.copy())
return
if _sum > target:
return
for i in range(start_index,len(candidates)):
_sum += candidates[i]
subset.append(candidates[i])
back_tracking(result, subset, candidates, target, _sum, i + 1)
_sum -= candidates[i]
subset.pop()
result = []
subset = []
_sum = 0
candidates.sort()
back_tracking(result, subset, candidates, target, _sum, 0)
return result
剪枝需要考虑的点:
1. 使用used数组来判断当前的值是否被用过了,如果没用过,说明在根节点,并且当前值与前一个值相等,就可以直接跳过,因为相同的情况在前一个值全都考虑过了。
如果用过的话,不能跳过,因为说明在叶子节点。
2.回溯的时候used数组要重新赋值为0
3. 因为不能重复选择当前值,所以每次递归的start_index为当前所取值i + 1
正确代码如下:
这是通过数组来去重,所以每次循环i都是从0开始
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
def back_tracking(result, subset, candidates, target, _sum, start_index):
if _sum > target:
return
elif _sum == target:
result.append(subset.copy())
return
for i in range(start_index,len(candidates)):
if i > start_index and candidates[i] == candidates[i - 1]:
continue
_sum += candidates[i]
subset.append(candidates[i])
back_tracking(result, subset, candidates, target, _sum, i + 1)
_sum -= candidates[i]
index = subset.pop()
result = []
subset = []
_sum = 0
candidates.sort()
back_tracking(result, subset, candidates, target, _sum, 0)
return result
另外通过start index去重就只要判断i > start_index就可以了, 相当于在叶子上去重
131. 分割回文串
class Solution:
def partition(self, s: str) -> List[List[str]]:
def isPalindrome(subsubset):
left = 0
right = len(subsubset) - 1
while(left <= right):
if subsubset[left] == subsubset[right]:
left += 1
right -= 1
else:
return False
return True
def back_tracking(result, subset, s, start_index):
if start_index >= len(s):
result.append(subset.copy())
return
for i in range(start_index, len(s)):
if i == start_index:
subsubset = s[i]
elif i > start_index:
subsubset = s[start_index:i + 1]
if isPalindrome(subsubset):
subset.append(subsubset)
else:
continue
back_tracking(result, subset, s, i + 1)
subset.pop()
result = []
subset = []
start_index = 0
back_tracking(result, subset, s, start_index)
return result
几个关键点:
1. 切割和组合类似,都是遍历的时候不能和当前元素重复,所以下一次递归的时候start_index就是i + 1
2. 切割的字串可以用start_index 到 i 的左闭右闭区间表示,因为python字符串的切片是左闭右开的,所以要写成[start_index: i+1]
3. 递归是走树的深度,循环是走树的宽度,所以代码运行的逻辑先是走递归走到底,就是最左边走到最深处,然后回溯返回的过程就是一步步的走树的宽度。