回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成的树的深度。
递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。
回溯的模板一般是
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
上一次刷的时候分过类,这次就按着这几个分类把回溯复习一遍
组合问题和切割问题收集的是叶子结点, 子集问题收集的是所有结点,这两种的每层递归逻辑里都需要一个start, 下层从上层start的下一个开始
全排列问题不需要start, 它是下一层也从头开始遍历。所以涉及了很多去重问题
如果说nums本身有很多重复,那么我们需要在每一层的遍历时去重, 可以排序之后去重也可以用一个set去重
如果说像子集问题需要在每个枝条上去重的话,有简单的if nums[i] in path,但是如果每层去重和枝条上去重混合的时候就不能用这个了,因为可能每一层就相同的数字,必须用used数组去回溯,标记每一位上一层是否用过,上一层用过的位置标1,下一层不能继续用
以上是我第一次刷完回溯篇的简单体会,放在第二次也适用
————————————————
版权声明:本文为CSDN博主「lebowskii」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lebowskii/article/details/127440612
组合
组合总和3
电话号码的字母组合
组合总和
组合总和2
candidates 中的每个数字在每个组合中只能使用 一次
candidates = [10,1,2,7,6,1,5], target = 8
答案:[[1,1,6], [1,2,5], [1,7], [2,6]]
由于样例中数字就有重复,且[1,2,5] 和 [2,1,5]属于同一个答案
首先呢,每个数字在每个组合只能使用一次,说明在一个path里可以有重复;而同一个path属于下一层的递归
而在各个path中,不能有重复,说明在每一层的遍历中,遇到相同的要去重
重复一遍,同一层的去重是因为各个path不能有重复的数;同一树枝的去重是因为在一个path中不能有重复
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
candidates.sort()
res = []
def dfs(start, cnt, path):
if cnt >= target:
if cnt == target:
res.append(path)
return
for i in range(start, len(candidates)):
if i > start and candidates[i] == candidates[i-1]: continue #各个path之间不能重复,同一层的去重
if cnt + candidates[i] > target: return #求和的一般可以这样剪枝
dfs(i+1, cnt+candidates[i], path+[candidates[i]])
dfs(0, 0, [])
return res
分割回文串
两个点:
1.写回溯的时候,我一开始没把sub_str抽出来,导致我不是很会写回溯,抽出来了就可以加法大法
2.写出口的时候,一开始我写成start == len(s)-1, 但是start到那个位置,path没有加上最后一个s[n-1]
def partition(self, s: str) -> List[List[str]]:
res = []
def check(s):
n = len(s)
l, r = 0, n-1
while l < r:
if s[l] == s[r]:
l += 1
r -= 1
else: return False
return True
def dfs(start, path):
if start >= len(s):
res.append(path[:])
return
for i in range(start, len(s)):
sub_str = s[start:i+1]
if check(sub_str):
dfs(i+1,path+[sub_str])
dfs(0, [])
return res
复原ip地址
和上面那个一样,我一遍就ac了
子集
子集 II
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort()
def dfs(start,path):
res.append(path)
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i-1]: continue
dfs(i+1,path+[nums[i]])
dfs(0,[])
return res
这道题写在这的原因是, 我判别出来这是在每一层的逻辑上去重;但是写 if nums[i] == nums[i-1]: continue的时候,忘记了写 i > start了,导致出错
每一次的区间其实是(start, len)
第一层(0, len)
第二层(1, len)
第三层(2, len)
在每一层里 去遍历 去去重,去判别是否nums[i] == nums[i-1], 所以说 一定要加上 i > start,不然拉不开
递增子序列
全排列
题目链接
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort()
flag = [True] * len(nums)
def dfs(flag,path):
if len(path) == len(nums):
res.append(path)
return
for i in range(len(nums)):
if flag[i]:
if i > 0 and nums[i] == nums[i-1] and flag[i-1]: continue
flag[i] = False
dfs(flag,path+[nums[i]])
flag[i] = True
dfs(flag,[])
return res
1.flag[]数组初始化在dfs内还是外
我们如果初始化这个数组在dfs函数内,每次我们递归调用dfs进下一层往深里搜的时候,都会初始化一次,肯定不对;要定义在dfs外部
2.每层的逻辑,只有flag[i]表示 可用,我们才能进行递归的逻辑
每一层的去重中, 我们必须加一个and flag[i]; 不然会出问题
N皇后
解数独