复习回溯法结构
在求解组合问题之前,需要将组合问题的树形图找出来,找出来树形图之后,再使用回溯法的结构模板进行求解。回溯算法的模板结构如下所示。
回溯三部曲:
- 确定函数的参数和返回值 ,函数的返回值一般来说是void类型
- 确定终止条件 ,if的终止逻辑
- 确定单层搜索的逻辑 ,for循环+处理结点+递归函数+撤销结点
39. 组合总和
**注意:**元素可以重复使用,剩余的节点中需要包括本次的元素
本题是 集合里元素可以用无数次,那么和组合问题的差别 其实仅在于 startIndex上的控制
- 在组合问题中,传入的startIndex是 i+1 ,因为之前出现过的节点,此后就不能出现了。
- 在组合问题的和中,传入的startIndex是 i ,因为需要能够访问该结点
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
# 注意可以使用多次
# 回溯的步骤复习
# if终止条件,if中放置相应的结果集
# 回溯函数中存放 for循环:处理结点-递归函数-撤销结点
result = []
path = []
# 进行单层的递归
def traversal(index):
if sum(path) == target:
result.append(path.copy())
return
# 进行剪枝,如果大于就不再乡向下继续进行查找。
if sum(path) > target:
return
# 不同之处在于,传入的startIndex的值不同
for i in range(index,len(candidates)):
path.append(candidates[i])
traversal(i)
path.pop()
return
traversal(0)
return result
40.组合总和II(排序+回溯)
思路: 搜索过程中的进行去重,之前出现过的元素
- 树层去重:
- 树枝去重:used数组,记录当前结点是否搜索过
树枝上可以重复取,树层上不可以重复取,因而本题的关键在于数层上的去重,而不是数枝的去重
树层去重: 在for循环中进行去重,如果当前结点和上一个节点相同,并且上一个节点没有被访问,这样就可以说明这个节点出现了重复。
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
# 不同之处在于需要对原来的数组进行排序的操作
# 利用快速排序
used = [0 for i in range(len(candidates))]
result = []
path = []
candidates = sorted(candidates) # 先进行排序
def traversal(index):
if sum(path) == target:
result.append(path.copy())
return
if sum(path) > target:
return
for i in range(index,len(candidates)):
# 树层去重的逻辑
if i >= 1 and candidates[i] == candidates[i-1] and used[i-1] == 0:
continue
path.append(candidates[i])
used[i] = 1
traversal(i+1)
path.pop()
used[i] = 0
return
traversal(0)
return result
131.分割回文串
切割问题,非常类似于组合问题。
代码里面什么表示切割线
(startIndex,i] 即为子串
// 确定递归的参数和返回值
// 全局变量 一维数组path:收集单一结果
// 二维数组result:将符合条件的结果储存起来
void backTracking(s,startIndex):
// startIndex就是表示切割的线,如果切割线走到了末尾,那么该结果进行判断
if startIndex >= s.size:
result.append(s)
return
for(i=startIndex,i<s.size,i++){
s = s[startIndex:i]
ifhuiwen(s)
backTracking(s,i+1)
// 再进行回溯求解。
}
整体思路:
- 如何遍历所有可以切割的位置:将startIndex转化成切割的位置,然后利用i进行比遍历,每次返回一个子串即可。
- 返回的过程中需要进行剪枝操作,只有当前的子串是回文串,才可以继续向下进行搜寻。
- 利用while循环判断当前子串是否为回文子串。
class Solution:
def partition(self, s: str) -> List[List[str]]:
# 回溯法分割回文子串
result = []
path = []
def isPalindrome(s,start,end):
# 判断是否为回文子串
while start < end :
if s[start] != s[end]:
return False
start += 1
end -= 1
return True
def backtracking(s,startIndex):
if startIndex >= len(s):
result.append(path.copy())
return
for i in range(startIndex,len(s)):
if isPalindrome(s,startIndex,i):
path.append(s[startIndex:i+1])
backtracking(s,i+1)
path.pop()
else:
continue
backtracking(s,0)
return result