回溯思路主要根据代码随想录回溯篇
组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等
代码模板:主要找到广度和深度
可以从图中看出for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。
res = []
path = []
def backtrack(input,path):
if (终止条件) {
存放结果;
return;
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
下面针对每道题进行详细的梳理
一、组合
组合
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
res = []
path = []
def backtrack(n, k, startindex, ):
if len(path) == k:
res.append(path[:])
return
for i in range(startindex, n - (k - len(path)) + 1 + 1): # 减枝操作
path.append(i)
backtrack(n, k, i + 1)
path.pop()
backtrack(n, k, 1)
return res
组合总和III
class Solution:
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
res = []
path = []
def backtrack(k, targetSum, startInedx):
if sum(path[:]) > targetSum:
return
if len(path) == k and sum(path[:]) == targetSum:
res.append(path[:])
return
for i in range(startInedx, 10):
path.append(i)
backtrack(k, targetSum, i + 1)
# k += 1
# n += i
path.pop()
backtrack(k, n, startInedx=1)
return res
电话号码的字母组合
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
const = {
'0': '',
'1': '',
'2': 'abc',
'3': 'def',
'4': 'ghi',
'5': 'jkl',
'6': 'mno',
'7': 'pqrs',
'8': 'tuv',
'9': 'wxyz'
}
res = []
if not digits:
return res
def backtrack(digits, index, s):
# 长度即为取的index+1
if index == len(digits):
res.append(s)
return
letter = const[digits[index]]
index += 1
for i in letter:
s += i
backtrack(digits, index, s)
s = s[:-1]
backtrack(digits, 0, s = '')
return res
组合总和
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
def backtrack(path, candidates):
if sum(path[:]) > target:
return
if sum(path[:]) == target:
res.append(path[:])
return
for i in range(len(candidates)):
path.append(candidates[i])
backtrack(path, candidates[i:])
path.pop()
backtrack(path=[], candidates=candidates)
return res
组合总和II
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
candidates = sorted(candidates)
def backtrack(path, candidates):
if sum(path[:]) > target:
return
if sum(path[:]) == target:
res.append(path[:])
return
for i in range(len(candidates)):
if i>0 and candidates[i-1]==candidates[i]:
continue
path.append(candidates[i])
backtrack(path, candidates[i+1:])
path.pop()
backtrack(path=[], candidates=candidates)
return res
二、分割
分割回文串
class Solution:
def huiwen(self, sub_s):
"""验证回文串"""
if sub_s:
for sub in sub_s:
if sub == sub[::-1]:
continue
else:
return False
return True
else:
return False
def partition(self, s: str) -> List[List[str]]:
res = []
init_len = len(s)
def backtrack(s, list_sub_s):
tmp_len = 0
for idx in list_sub_s:
tmp_len += len(idx)
if tmp_len == init_len:
res.append(list_sub_s[:])
return
for i in range(len(s)+1):
sub = s[:i]
if sub==sub[::-1] and sub!='':
list_sub_s.append(s[:i])
backtrack(s[i:], list_sub_s)
list_sub_s.pop()
backtrack(s, [])
复原IP地址
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
res = []
def backtrack(path, s):
if 4 - len(path.split('.')) > len(s):
return
if path!='':
for i in path.split('.'):
if int(i)>255:
return
if len(path.split('.')) == 4 and s == '':
res.append(path)
return
for i in range(len(s)):
if path != '':
path += '.'
if len(s[:i+1]) == len(str(int(s[:i + 1]))):
path += str(int(s[:i + 1]))
backtrack(path, s[i + 1:])
path = path[:-len(str(int(s[:i+1])))]
path = path[:-1]
backtrack('', s)
return res
三、子集
子集II
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
nums = sorted(nums)
res = []
def backtrack(nums,path):
res.append(path[:])
for i in range(len(nums)):
if i>0 and nums[i-1]==nums[i]:
continue
path.append(nums[i])
backtrack(nums[i+1:],path)
path.pop()
backtrack(nums,[])
return res
递增子序列
class Solution:
def findSubsequences(self, nums: List[int]) -> List[List[int]]:
res = []
def backtrack(nums,path):
if len(path)>1:
res.append(path[:])
# return # 这里不return,因为还要继续往下寻找,知道找到叶子节点,自动停止
uset = set() # 记录本层用过的数字,后面不能再用了
for i in range(len(nums)):
if (path and path[-1] > nums[i]) or nums[i] in uset:
continue
uset.add(nums[i])
path.append(nums[i])
backtrack(nums[i+1:],path)
path.pop()
backtrack(nums,[])
return res
四、排列
全排列
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
lens = len(nums)
def backtrack(nums,path):
if len(path) == lens:
res.append(path[:])
return
for i in range(len(nums)):
path.append(nums[i])
backtrack(nums[:i]+nums[i+1:],path)
path.pop()
backtrack(nums,[])
return res
全排列 II
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
res = []
lens = len(nums)
def backtrack(nums, path):
if len(path) == lens:
res.append(path[:])
return
uset = set()
for i in range(len(nums)):
if nums[i] in uset:
continue
uset.add(nums[i])
path.append(nums[i])
backtrack(nums[:i] + nums[i + 1:], path)
path.pop()
backtrack(nums, [])
return res
五、棋盘问题
N皇后
递归深度就是row控制棋盘的行
,每一层里for循环的col控制棋盘的列
,一行一列,确定了放置皇后的位置。
每次都是要从新的一行的起始位置开始搜,所以都是从0开始。
按照如下标准去重:
- 不能同行
- 不能同列
- 不能同斜线 (45度和135度角)
class Solution(object):
def isValid(self,row,col,chessboard): # 验证棋盘是否合法
# 判断45度
i,j = row-1,col-1
while i>=0 and j>=0:
if chessboard[i][j] == 'Q':
return False
i-=1
j-=1
# # 判断行
for i in range(col):
if chessboard[row][i] == 'Q':
return False
# 判断列
for i in range(row):
if chessboard[i][col] == 'Q':
return False
# 判断135度
i, j = row - 1, col + 1
while i >= 0 and j < len(chessboard):
if chessboard[i][j] == 'Q':
return False # 右上方向已经存在皇后,不合法
i -= 1
j += 1
return True
def solveNQueens(self, n):
"""
:type n: int
:rtype: List[List[str]]
"""
res = []
chessboard = ['.'* n for _ in range(n)]
def backtrack(row,chessboard): # 从0行开始
if row == n:
res.append(chessboard[:])
return
for col in range(n): # 宽度搜寻列
if self.isValid(row,col,chessboard):
chessboard[row] = chessboard[row][:col]+'Q'+chessboard[row][col+1:]
backtrack(row+1,chessboard)
chessboard[row] =chessboard[row][:col]+'.'+chessboard[row][col+1:]
backtrack(0,chessboard)
return res
解数独
六、其他
重新安排行程
这道题目有几个难点:
一个行程中,如果航班处理不好容易变成一个圈,成为死循环
有多种解法,字母序靠前排在前面,让很多同学望而退步,如何该记录映射关系呢 ?
使用回溯法(也可以说深搜) 的话,那么终止条件是什么呢?
搜索的过程中,如何遍历一个机场所对应的所有机场。
本题以输入:[[“JFK”, “KUL”], [“JFK”, “NRT”], [“NRT”, “JFK”]为例,抽象为树形结构如下:
class Solution(object):
def findItinerary(self, tickets):
"""
:type tickets: List[List[str]]
:rtype: List[str]
"""
tickets = sorted(tickets)
res = []
path = ['JFK']
lens = len(tickets)
used = [0]*lens # 用于存在是否使用过的状态
def backtrack(tickets,path,cur):
if len(path) == lens+1:
res.append(path[:])
return True
# 补充返回的情况
for i,ticket in enumerate(tickets):
if ticket[0] == cur and used[i]==0: # 如果当前机票的起点等于上一个终点,且该机票未使用
used[i] = 1 # 标记为已使用
path.append(ticket[1])
stack = backtrack(tickets,path,ticket[1])
path.pop()
used[i] = 0 # 还原为未使用
if stack: # 如果找到了一条路径,就不继续搜索了。 否则会超时。
return True
backtrack(tickets,path,"JFK")
return res[0]