回溯法(以DFS为例)例题总结
框架:
#定义一个结果数组
self.result = []
def dfs(candidates,res):
#满足题目,返回条件
if target==sum(res):
self.result.append(res[:])
for i in range(len(candidates)):
#临时结果添加
res.append(candidates[i])
#下一步的访问路径(非必须,若无则是原数组)
list1 = candidates[i:]
#递归
dfs(list1,res)
#移除选择
res.remove(candidates[i])
#调用函数
dfs(candidates,[])
return self.result
至于什么时候用self.result.append(res[:]),什么时候用self.result.append(res),取决于你的调用函数的传入参数,若传入参数中有res时,就可以使用self.result.append(res),若没有就只能使用self.result.append(res[:]).
例题一:
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
self.result = []
def dfs(candidates,res):
#因为我们的目标是target==当前res的和,
#所以只要当前res和大于target就应该return
if target<sum(res):
return
if target==sum(res):
self.result.append(res[:])
for i in range(len(candidates)):
res.append(candidates[i])
list1 = candidates[i:]
dfs(list1,res)
res.remove(candidates[i])
dfs(candidates,[])
return self.result
例题二:
若上面的数组中有重复数字,且解集不能有重复数组。
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
代码:(条件判断和排序操作是为了防止结果数组中有重复,list1 = candidates[i+1:]是为了不反复取当前元素)
class Solution:
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
self.result = []
def dfs(candidates,res):
if target < sum(res):
return
if target == sum(res):
self.result.append(res[:])
for i in range(len(candidates)):
#数组常见去重复的方法,对于重复的数值,
#我们只让第一个进入循环,后面的就不要再进入循环了
if i > 0 and candidates[i-1] == candidates[i]:
continue
res.append(candidates[i])
list1 = candidates[i+1:]
dfs(list1,res)
res.remove(candidates[i])
#必须排序,这样前一位等于后一位的时候就是重复了!
candidates.sort()
dfs(candidates,[])
return self.result
例题三:(标准套路)
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
self.result = []
size = len(nums)
def dfs(res):
if len(res)==size:
self.result.append(res[:])
return
for i in range(len(nums)):
if nums[i] in res:
continue
res.append(nums[i])
dfs(res)
res.pop()
dfs([])
return self.result
例题四:(与上面的去重不同的是,全排列问题,每次返回的数组还是原数组)
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
self.result = []
size = len(nums)
def dfs(res):
if len(res)==size:
self.result.append(res[:])
return
for i in range(len(nums)):
#去掉相邻重复元素数组
if i>0 and nums[i]==nums[i-1] and not list1[i-1]:
continue
if list1[i]:
continue
#是否选用,建立标志位
list1[i] = True
res.append(nums[i])
dfs(res)
list1[i] = False
res.pop()
list1 = [False for _ in range(len(nums))]
nums.sort()
dfs([])
return self.result
特殊一:
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
res = []
curstr = ''
def dfs(curstr,left,right):
if left==0 and right == 0:
res.append(curstr)
return res
if left>0:
dfs(curstr+'(',left-1,right)
if left<right:
dfs(curstr+')',left,right-1)
return curstr
dfs(curstr,n,n)
return res
特殊二:
N皇后问题
class Solution:
def __init__(self):
self.result = list()
self.num = 0
def solveNQueens(self, n: int) -> List[List[str]]:
board = ['.'*n] *n
self.backTrace(board,0)
return self.result
def backTrace(self,board,row):
n = len(board)
if row == n:
# print('board:',board)
self.result.append(board[:])
self.num +=1
return
for col in range(n):
if not self.isValid(board,row,col):
continue
board[row] = '.'*col + 'Q' + '.'*(n-col-1)
self.backTrace(board,row+1)
board[row] = '.'*n
def isValid(self,board,row,col):
n = len(board)
#列
for i in range(n):
if board[i][col] == 'Q':
return False
#右上角
for i,j in zip(range(row-1,-1,-1), range(col+1,n)):
if board[i][j] == 'Q':
return False
#左上方
for i, j in zip(range(row - 1, -1, -1), range(col-1, -1,-1)):
if board[i][j] == 'Q':
return False
return True
特殊四:(类似于特殊1)
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成),整数之间用 ‘.’ 分隔。
示例:
输入: “25525511135”
输出: [“255.255.11.135”, “255.255.111.35”]
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
res = []
if len(s)>12 or len(s)<4:
return []
def isvalid(ip):
if ip.count('.') != 3:
return False
x = ip.split('.')
for i in x:
if not i or int(i)>255 or (len(i)>1 and i[0]=='0'):
return False
return True
def dfs(cur,idx,cnt):
if cnt == 3:
if isvalid(cur):
res.append(cur[:])
return
if idx > len(cur)-1:
return
dfs(cur[:idx]+'.'+cur[idx:],idx+2,cnt+1)
dfs(cur,idx+1,cnt)
dfs(s,0,0)
return res
求和为n,且元素的最大值小于k大于d的所有组合。(不去重)
不去重就是说:112和211是两个组合。
n,k,d = list(map(int,input().strip().split()))
res = 0
def dfs(n,k,d,flag):
if n==0 and flag:
return 1
if n<0:
return 0
for i in range(1,k+1):
if i>=d:
flag = True
res += dfs(n-i,k,d,flag)
return res
print(dfs(n,k,d,False))
去重的写法,之所以循环中是start是为了去重(只选择最后一位的数字大小,上面不去重的写法是随便选择一个数字。)
def helper():
log = []
def calc(n,start,k,d,flag):
if n==0 and flag:
print(log)
else:
for i in range(start,n+1):
if i>=d and i<=k:
flag = True
else:
flag = False
log.append(i)
calc(n-i,i,k,d,flag)
log.pop()
calc(5,1,3,2,0)
print(helper())
934 最短的桥
在给定的二维二进制数组 A 中,存在两座岛。(岛是由四面相连的 1 形成的一个最大组。)
现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。
返回必须翻转的 0 的最小数目。(可以保证答案至少是 1 。)
示例 1:
输入:A = [[0,1],[1,0]]
输出:1
示例 2:
输入:A = [[0,1,0],[0,0,0],[0,0,1]]
输出:2
示例 3:
输入:A = [[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
输出:1
class Solution:
def shortestBridge(self, grid: List[List[int]]) -> int:
row = len(grid)
col = len(grid[0])
queues = collections.deque()
find = False
def bfs(i,j):
if i<0 or i>=row or j<0 or j>=col or grid[i][j] == 0:
return
grid[i][j] = 0
queues.append((i,j))
for x,y in [(i-1,j),(i+1,j),(i,j-1),(i,j+1)]:
bfs(x,y)
for i in range(row):
for j in range(col):
if grid[i][j] == 1 and not find:
bfs(i,j)
find = True
seen = set(queues)
level = 0
while queues:
size = len(queues)
for s in range(size):
(l,r) = queues.popleft()
for x,y in [(l-1,r),(l+1,r),(l,r-1),(l,r+1)]:
if x<0 or x>=row or y<0 or y>=col or (x,y) in seen:
continue
if grid[x][y] == 0:
queues.append((x,y))
seen.add((x,y))
else:
return level
level +=1
return level
113 路径总和②
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
示例 2:
输入:root = [1,2,3], targetSum = 5
输出:[]
示例 3:
输入:root = [1,2], targetSum = 0
输出:[]
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
res = []
result = []
def dfs(root,targetSum):
if not root:
return 0
targetSum -= root.val
res.append(root.val)
if not root.left and not root.right:
if targetSum == 0:
result.append(res[:])
dfs(root.left,targetSum)
dfs(root.right,targetSum)
res.pop()
dfs(root,targetSum)
return result
124 二叉树中的最大路径和
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和 是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def maxPathSum(self, root: TreeNode) -> int:
res = []
max_pro = -inf
def dfs(root,max_pro):
if not root:
return 0
left = max(dfs(root.left,max_pro),0)#注意要与0取max
right = max(dfs(root.right,max_pro),0)
temp_max = root.val + left + right#储存临时最大值
max_pro = max(max_pro,temp_max)
res.append(max_pro)
return root.val + max(left,right)#递归计算中的最大值
dfs(root,max_pro)
return max(res)
685 冗余连接②
在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。
返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。
class UnionFind:
def __init__(self,n):
self.parent = list(range(n))
def find(self,node):
if self.parent[node] != node:
self.parent[node] = self.find(self.parent[node])
return self.parent[node]
def union(self,node1,node2):
self.parent[self.find(node1)] = self.find(node2)
class Solution:
def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
count = len(edges)
uf = UnionFind(count+1)
parent = list(range(count+1))
conflict = -1
cycle = -1
for idx,(x,y) in enumerate(edges):
#节点重复出现,导致冲突,对应示例1
if parent[y] != y:
conflict = idx
else:
parent[y] = x
#祖先相同,环路出现,对应示例1和示例2
if uf.find(x) == uf.find(y):
cycle = idx
else:
uf.union(x,y)
if conflict<0:
return [edges[cycle][0],edges[cycle][1]]
else:
conflictedge = edges[conflict]
if cycle>0:
return [parent[conflictedge[1]],conflictedge[1]]
else:
return [conflictedge[0],conflictedge[1]]
332 重新安排行程
给你一份航线列表 tickets ,其中 tickets[i] = [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。
所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。如果存在多种有效的行程,请你按字典排序返回最小的行程组合。
例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前。
假定所有机票至少存在一种合理的行程。且所有的机票 必须都用一次 且 只能用一次。
class Solution:
def findItinerary(self, tickets: List[List[str]]) -> List[str]:
tickets_dict = defaultdict(list)
path = []
for item in tickets:
tickets_dict[item[0]].append(item[1])
n = len(tickets)
ori_point = 'JFK'
path.append(ori_point)
def dfs(ori_point):
if len(path) == n+1:
return True
tickets_dict[ori_point].sort()
for _ in tickets_dict[ori_point]:
new_point = tickets_dict[ori_point].pop(0)
path.append(new_point)
if dfs(new_point):
return True
path.pop()
tickets_dict[ori_point].append(new_point)
dfs(ori_point)
return path
365 水壶问题
有两个容量分别为 x升 和 y升 的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?
如果可以,最后请用以上水壶中的一或两个来盛放取得的 z升 水。
你允许:
装满任意一个水壶
清空任意一个水壶
从一个水壶向另外一个水壶倒水,直到装满或者倒空
示例 1: (From the famous “Die Hard” example)
输入: x = 3, y = 5, z = 4
输出: True
示例 2:
输入: x = 2, y = 6, z = 5
输出: False
class Solution:
def canMeasureWater(self, jug1Capacity: int, jug2Capacity: int, targetCapacity: int) -> bool:
stack = [(0,0)]
self.seen = set()
while stack:
remain_x,remain_y = stack.pop()
if remain_x == targetCapacity or remain_y == targetCapacity or remain_y+remain_x == targetCapacity:
return True
if (remain_x,remain_y) in self.seen:
continue
self.seen.add((remain_x,remain_y))
stack.append((remain_x,0))
stack.append((0,remain_y))
stack.append((jug1Capacity,remain_y))
stack.append((remain_x,jug2Capacity))
stack.append((remain_x-min(remain_x,jug2Capacity-remain_y),remain_y+min(remain_x,jug2Capacity-remain_y)))
stack.append((remain_x+min(remain_y,jug1Capacity-remain_x),remain_y-min(remain_y,jug1Capacity-remain_x)))
return False
419 甲板上的战舰
给定一个二维的甲板, 请计算其中有多少艘战舰。 战舰用 'X’表示,空位用 '.'表示。 你需要遵守以下规则:
给你一个有效的甲板,仅由战舰或者空位组成。
战舰只能水平或者垂直放置。换句话说,战舰只能由 1xN (1 行, N 列)组成,或者 Nx1 (N 行, 1 列)组成,其中N可以是任意大小。
两艘战舰之间至少有一个水平或垂直的空位分隔 - 即没有相邻的战舰。
示例 :
X…X
…X
…X
在上面的甲板中有2艘战舰。
无效样例 :
…X
XXXX
…X
你不会收到这样的无效甲板 - 因为战舰之间至少会有一个空位将它们分开。
进阶:
你可以用一次扫描算法,只使用O(1)额外空间,并且不修改甲板的值来解决这个问题吗?
class Solution:
def countBattleships(self, board: List[List[str]]) -> int:
row = len(board)
col = len(board[0])
flag = 0
self.res = 0
def dfs(x):
for y in range(col):
if board[x][y] == 'X':
bfs(x, y)
self.res +=1
def bfs(x,y):
if 0<=x<row and 0<=y<col and board[x][y] == 'X':
board[x][y] = '.'
for i,j in [(1,0),(0,1)]:
bfs(x+i,y+j)
for x in range(row):
dfs(x)
return self.res