回溯算法的理解
算法理解
回溯算法,其实就是指导遍历一棵多叉树。
回溯函数中含有循环和递归,循环是控制树的横向遍历,递归是控制树的纵向遍历,其本质是深度优先搜索。
一般用于排列组合问题。
回溯函数的一般结构
def backtracking(一些参数):# ① 参数的设置,否设置start_index 参数
if 循环终止条件:
收集结果
return
for i in range(层序循环):# ②循环空间的设置(含一些剪枝操作)
一些剪枝操作# ③有时候仅在循环处就可以控制剪枝操作)
加入节点,及其相关操作
backtracking(一些参数) # ④start_index参数是否需要改变
弹出节点,及其相关操作
回溯算法要点
- 把问题变成一棵树
- 确定循环空间的设置
- 确定加入和弹出节点有哪些操作
- 确定循环终止条件
- 剪枝策略
真题演练
77.组合
combinations
剪枝策略: n − x + 1 > = k − l e n ( p a t h ) n-x+1>=k-len(path) n−x+1>=k−len(path),然后python循环的经典+1
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
path=[]
res=[]
def backtracking(n,k,start_index):
if len(path)==k:
res.append(path[:])
return
for i in range(start_index,n+2-k+len(path)):
path.append(i)
backtracking(n,k,i+1) # 注意,这里是i 不是start_index
path.pop()
backtracking(n,k,1)
return res
216 组合总和Ⅲ
https://leetcode.cn/problems/combination-sum-iii/
树和上个题一样,终止条件不同
class Solution:
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
res = []
path = []
self.sum_now=0
def backtracking(k: int, n: int, startindex: int):
if len(path) == k:
if sum(path) == n:
res.append(path[:])
return
for i in range(startindex,11-k+len(path)):
if self.sum_now > n:
break
path.append(i)
self.sum_now+=i
backtracking(k, n, i + 1)
path.pop()
self.sum_now-=i
backtracking(k, n, 1)
return res
17.电话号码的字母组合
https://leetcode.cn/problems/letter-combinations-of-a-phone-number/
因为列表间不重复,所以不需要start_index 这个东西
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
n=len(digits)
if n==0:
return []
phone={
2:'abc',3:'def',4:'ghi',5:'jkl',6:'mno',7:'pqrs',8:'tuv',9:'wxyz'}
path=[]
res=[]
def backtracking(digits,n,start_index):
if len(path)==n:
res.append("".join(path[:]))
return
for i in phone[int(digits[start_index])]:
path.append(i)
backtracking(digits,n,start_index+1)
path.pop()
backtracking(digits,n,0)
return res
39. 组合总和
循环的每一层用的同一个list,所以需要start_index作为参数传入,
这个题用过的数字还可以再用,故回溯时候不用 i+1,
sort()一下方便剪枝,会更快。
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
path=[]
res=[]
self.sum=0
n=len(candidates)
candidates.sort()
def bcaktracking(candidates,n,target,start_index):
if self.sum==target:
res.append(path[:])
return
for i in range(start_index,n):
if self.sum+candidates[i]>target:
break
path.append(candidates[i])
self.sum+=candidates[i]
bcaktracking(candidates,n,target,i)
path.pop()
self.sum-=candidates[i]
bcaktracking(candidates,n,target,0)
return res
40 组合总和Ⅱ
https://leetcode.cn/problems/combination-sum-ii/
没有去重的后果:
比如输入是[1,1,2,6] target=7
那么会输出[1,2,6],[1,2,6],
但其实两个1是一样的,故是重复的,会报错。
解释一下去重关键步骤:
start_index
其实控制的是层数,
i>start_index
就是指在start_index
这一层的后面其他数字
candidates[i]==candidates[i-1]
表示数值和前一位数值一样(因为事先有过sort
的步骤了)
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
candidates.sort()
res=[]
path=[]
self.sum=0
def backtracking(candidates,target,start_index):
if self.sum==target:
res.append(path[:])
for i in range(start_index,len(candidates)):
if self.sum+candidates[i]>target:
return
if i>start_index and candidates[i]==candidates[i-1]:# 去重关键步骤
continue
path.append(candidates[i])
self.sum+=candidates[i]
backtracking(candidates,target,i+1)
path.pop()
self.sum-=candidates[i]
backtracking(candidates,target,0)
return res
131. 分割回文串
这个题和前面不一样,如何转化成树也是一个难点。
题目原文
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例: 输入: “aab” 输出: [ [“aa”,“b”], [“a”,“a”,“b”] ]
思路:
这个和中学的排列组合题有点像,
切开字符串就相当于在字符串里插入断点/隔板,
每一层分别插入隔板数量分别为1,2,3…,
子节点是在当前状态下,在此隔板的后方再插入隔板。
那么具体操作时,还需判断递归终止条件和剪枝策略。
终止条件:后面不能再放隔板了start_index==len(s)
,即为终止。
**剪枝:**因为下一层隔板放在第一层最后一个隔板的后面,故对前面的字符串没有改变,故若前面字符temp=s[start_index:i+1]
串非回文,就可以剪枝了,此处用的continue
。
class Solution:
def partition(self, s: str) -> List[List[str]]:
n=len(s)
path=[]
res=[]
# def is_palindrome(str):
# i, j = 0, len(str)-1
# while i < j:
# if str[i] != str[j]:
# return False
# i += 1
# j -= 1
# return True
def backtracking(s,start_index):
if start_index==len(s):
res.append(path[:])
return
for i in range(start_index,len(s)):
temp=s[start_index:i+1]
if temp==temp[::-1]: # 判断回文的方式1:字符串倒序看是否一致。
# if is_palindrome(temp):# 判断回文的方式2:根据定义写函数。
path.append(temp)
backtracking(s,i+1)
path.pop()
else:
continue
backtracking(s,0)
return res
93. 复原 IP 地址
https://leetcode.cn/problems/restore-ip-addresses/
和回文子串相同,也是经典的插隔板问题。
题目原文
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效的 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “[email protected]” 是 无效的 IP 地址。