dfs的写法包括回溯和普通的深度优先搜索。
两者本质上是一样的,排列组合子集问题本文的回溯法,先append当前路径,然后再pop,和把当前路径传入到函数参数里本质上是一样的,就是写法不一样而已。参见括号生成的两种写法。
组合总和的添加结果是在for循环中,因为和target有关系,子集和全排列添加结果是在for循环外边。
排列类dfs和组合类dfs的区别
LintCode-15: Permutations (排列类DFS经典题!)_roufoo的博客-CSDN博客
回溯
51. N 皇后
Leetcode 51. N皇后(回溯) —— python_missTu~的博客-CSDN博客
class Solution(object):
def solveNQueens(self, n):
board, ret = [['.'] * n for _ in range(n)], []
self.dfs(board, n, 0, ret)
return ret
def dfs(self, board, n, row, ret):
if row == n:
ret.append(["".join(i) for i in board])
return
#核心方法是判断指定row和col能否放,能放就递归row+1的所有列,不能放就回溯
for i in range(n):
if not self.canPlace(row, i, n, board):
continue
board[row][i] = 'Q'
self.dfs(board, n, row + 1, ret)
board[row][i] = '.'
#判断当前情况下能否放置皇后
def canPlace(self, row, col, n, board):
for i in range(1, row + 1):
# 判断同一列上是否有Q
if board[row - i][col] == 'Q':
return False
# 判断逆对角线是否有Q
if col - i >= 0 and board[row - i][col - i] == 'Q':
return False
# 判断正对角线是否有Q
if col + i < n and board[row - i][col + i] == 'Q':
return False
return True
39. 组合总和
class Solution(object):
def combinationSum(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
if len(candidates)==0:return []
ans=[]
def backtrack(path,summ,index):
if summ==target:
ans.append(path)
elif summ>target:
return
elif summ<target:
for i in range(index,len(candidates)):
backtrack(path+[candidates[i]],summ+candidates[i],i)
backtrack([],0,0)
return ans
class Solution(object):
def helper(self, candidates, target, res, tmp, level):
for i in range(level, len(candidates)):
tmp.append(candidates[i])
if candidates[i] < target:
self.helper(candidates, target - candidates[i], res, tmp, i)
elif candidates[i] == target:
res.append(tmp[:])
print(tmp)
tmp.pop()
def combinationSum(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
res = []
self.helper(candidates, target, res, [], 0)
return res
40. 组合总和 II
class Solution(object):
def helper(self,candidates,target,res,tmp,level):
for i in range(level,len(candidates)):
tmp.append(candidates[i])
if candidates[i]<target:
self.helper(candidates,target-candidates[i],res,tmp,i+1)
elif candidates[i]==target and tmp[:] not in res:
res.append(tmp[:])
tmp.pop()
def combinationSum2(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
#与组合1不同的地方是,level往下循环不能包括现在的level.
#要判断重复的情况
candidates.sort()
res=[]
self.helper(candidates,target,res,[],0)
return res
------------------------------------v2
注意两个地方,
1个是在深搜之前要进行排列,否则会出现[1,7],[7,1]这种情况。
2是要做剪枝操作。
if i>level and candidates[i]==candidates[i-1]:
continue
不然重复元素太多的话会超时的。注意要i大于level的时候才跳过,不然就不是当前的层了。
class Solution(object):
def helper(self, candidates, target, res, tmp, level):
for i in range(level, len(candidates)):
if i>level and candidates[i]==candidates[i-1]:
continue
tmp.append(candidates[i])
if candidates[i] < target:
self.helper(candidates, target - candidates[i], res, tmp, i + 1)
elif candidates[i] == target and tmp[:] not in res:
res.append(tmp[:])
tmp.pop()
def combinationSum2(self, candidates, target):
"""
:type candidates: List[int]
:type target: int
:rtype: List[List[int]]
"""
candidates.sort()
res = []
self.helper(candidates, target, res, [], 0)
return res
46. 全排列
class Solution(object):
def permute(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
if len(nums) == 0:
return []
res = []
self.helper(nums, res, [], 0)
return res
def helper(self, nums, res, tmp, level):
if len(tmp) == len(nums):
res.append(tmp[:])
for i in range(0, len(nums)):
if nums[i] not in tmp:
tmp.append(nums[i])
self.helper(nums, res, tmp, i + 1)
tmp.pop()
v2
class Solution(object):
def helper(self, nums, res, tmp):
if len(tmp) == len(nums):
res.append(tmp[:])
for i in range(0, len(nums)):
if nums[i] not in tmp:
tmp.append(nums[i])
self.helper(nums, res, tmp)
tmp.pop()
def permute(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
if len(nums) == 0:
return []
res = []
self.helper(nums, res, [])
return res
47 全排列2
用visited数组来防止往回走。
class Solution(object):
def helper(self,nums,res,tmp,visited):
if len(tmp)==len(nums) and tmp not in res:
res.append(tmp[:])
for i in range(0,len(nums)):
if visited[i] == False:
tmp.append(nums[i])
visited[i] = True
self.helper(nums,res,tmp,visited)
tmp.pop()
visited[i] = False
def permuteUnique(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
nums.sort()
res=[]
visited=[False for _ in range(len(nums))]
self.helper(nums,res,[],visited)
return res
78. 子集
leetcode 78. 子集(python回溯法)_vs974532452的博客-CSDN博客
class Solution(object):
def subsets(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
if len(nums) == 0:
return []
res = []
self.dfs(nums, res, [], 0)
return res
def dfs(self,nums,res,tmp,level):
if len(tmp) <= len(nums):
res.append(tmp[:])
for i in range(level, len(nums)):
tmp.append(nums[i])
self.dfs(nums, res, tmp, i + 1)
tmp.pop()
90 子集2
class Solution(object):
def helper(self,nums,res,tmp,level):
if len(tmp) <= len(nums) and tmp not in res:
res.append(tmp[:])
for i in range(level,len(nums)):
if i>level and nums[i]==nums[i-1]:
continue
else:
tmp.append(nums[i])
self.helper(nums, res, tmp, i + 1)
tmp.pop()
def subsetsWithDup(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
if len(nums) == 0:
return []
res = []
nums.sort()
self.helper(nums, res, [], 0)
return res
全排列组合总和子集问题总结
1.全排列问题,在for循环的时候是从0开始,组合和子集问题是从index开始
2.组合总和和子集问题,如果数据无序的,先排序。
3.全排列问题,如果有重复元素,可以用visited来防止往回遍历。
4.子集和组合总和问题,可在for循环中,num[i]==nums[i-1]来剪枝。注意开始位置。
深度优先搜索
301. 删除无效的括号
题解:
花花酱 LeetCode 301. Remove Invalid Parentheses - 刷题找工作 EP139_哔哩哔哩_bilibili
1.判断括号合法的条件有两个:
a.右括号数量小于等于左括号的数量(i<n-1)
b.右括号数量等于左括号的数量(i=n-1)
2.计算需要移除的左括号和右括号的数量:
a.根据条件1,从左往右扫,右括号数量大于左括号的数量。
b.遍历完了之后,左括号右括号多几个。
3.递归,先移除右括号,保证前缀合法,再移除左边括号。
class Solution:
def removeInvalidParentheses(self, s: str) -> List[str]:
# 首先确定多余的括号:
cntleft, cntright = 0, 0
for char in s:
if char == "(":
cntleft += 1
if char == ")":
if cntleft == 0:
cntright += 1
else:
cntleft -= 1
self.res = []
self.dfs(s, 0, cntleft, cntright)
return self.res
# 以递归求解 helper 表示从start开始,移除s中的cntleft个和cntright个左/右括号,其实也是DFS不停向下遍历
def dfs(self, s, start, cntleft, cntright):
# 终止条件
if cntleft==0 and cntright==0 and self.valid(s):
self.res.append(s)
return
# 未达到终止条件,缩小问题:
for i in range(start, len(s)):
# 对于多个相同的半括号在一起,只删除第一个,算一次就行了
if i>start and s[i]==s[i-1]:
continue
# 遍历s,在所有的左括号的地方,移除当前点的左括号,然后问题在当前点移除做括号后,缩小为从这个点开始,再移除后面的cntleft-1个和cntright个括号
if s[i] == "(" and cntleft>0:
self.dfs(s[0:i]+s[i+1:], i, cntleft-1, cntright)
# 遍历s,在所有的右括号的地方,移除当前点的右括号,然后问题在当前点移除做括号后,缩小为从这个点开始,再移除后面的cntleft个和cntright-1个括号
elif s[i] == ")" and cntright>0:
self.dfs(s[0:i]+s[i+1:], i, cntleft, cntright-1)
def valid(self, s):
stack = []
for char in s:
if char not in ["(", ")"]:
continue
if char == "(":
stack.append(")")
elif len(stack) == 0:
return False
else:
stack.pop()
return len(stack)==0
作者:oliver8641
链接:https://leetcode-cn.com/problems/remove-invalid-parentheses/solution/301-remove-invalid-parentheses-by-oliver8641/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
BFS
class Solution:
def removeInvalidParentheses(self, s: str) -> List[str]:
from collections import deque
res = set()
# 删除左括号的个数
rmL = 0
# 删除右括号的个数
rmR = 0
for a in s:
if a == "(":
rmL += 1
elif a == ")":
if rmL != 0:
rmL -= 1
else:
rmR += 1
# 是否满足有效括号
def isValid(s):
cnt = 0
for a in s:
if a == "(":
cnt += 1
elif a == ")":
cnt -= 1
if cnt < 0: return False
return True
# 记录此时 s , 左右括号的个数
queue = deque([(s, rmL, rmR)])
visited = set()
visited.add((s, rmL, rmR))
while queue:
tmp_s, left_p, right_p = queue.pop()
# 输出条件
if left_p == 0 and right_p == 0 and isValid(tmp_s):
res.add(tmp_s)
for i in range(len(tmp_s)):
# 为字母时候
if tmp_s[i] not in "()": continue
if tmp_s[i] == "(" and left_p > 0:
t = tmp_s[:i] + tmp_s[i + 1:]
if (t, left_p - 1, right_p) not in visited:
queue.appendleft((t, left_p - 1, right_p))
visited.add((t, left_p - 1, right_p))
if tmp_s[i] == ")" and right_p > 0:
t = tmp_s[:i] + tmp_s[i + 1:]
if (t, left_p, right_p - 1) not in visited:
queue.appendleft((t, left_p, right_p - 1))
visited.add((t, left_p, right_p - 1))
return list(res)
作者:powcai
链接:https://leetcode-cn.com/problems/remove-invalid-parentheses/solution/dfs-bfs-by-powcai-4/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
22 括号生成
https://segmentfault.com/a/1190000022110082
cur路径单独append再pop的写法
Leetcode练习(Python):回溯算法类:第22题:括号生成:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 - 桌子哥 - 博客园
在函数里传参的写法
Python|回溯算法解括号生成问题_算法与编程之美-CSDN博客
结合树结构图理解dfs的过程
from typing import List
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
res = []
cur_str = ''
def dfs(cur_str, left, right, n):
"""
:param cur_str: 从根结点到叶子结点的路径字符串
:param left: 左括号已经使用的个数
:param right: 右括号已经使用的个数
:return:
"""
if left == n and right == n:
res.append(cur_str)
return
if left < right:
return
if left < n:
dfs(cur_str + '(', left + 1, right, n)
if right < n:
dfs(cur_str + ')', left, right + 1, n)
dfs(cur_str, 0, 0, n)
return res
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/generate-parentheses/solution/hui-su-suan-fa-by-liweiwei1419/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
140. 单词拆分 II
回溯剪枝
不剪枝的话,直接回溯,会超时
class Solution(object):
def __init__(self):
self.res=[]
def dfs(self,s,path,wordDict):
if len(s)==0:
self.res.append(path[:len(path)-1])
for i in range(len(s)+1):
cur=s[:i]
if cur in wordDict:
self.dfs(s[i:],path+cur+" ",wordDict)
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: List[str]
"""
self.dfs(s,"",wordDict)
return self.res
剪枝,memo[i]
代表了s[i:]
这个字符串能否被分割,如果不能,那就停止后续的所有遍历。
class Solution(object):
def __init__(self):
self.res=[]
def dfs(self,s,start,path,wordDict,memo):
count=len(self.res)
if start==len(s):
self.res.append(path[:len(path)-1])
for i in range(start,len(s)+1):
cur=s[start:i]
if cur in wordDict and memo[i]:
self.dfs(s,i,path+cur+" ",wordDict,memo)
if len(self.res)>count:
memo[start]=1
else:
memo[start]=0
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: List[str]
"""
memo=[1 for _ in range(len(s)+1)]
self.dfs(s,0,"",wordDict,memo)
return self.res
139. 单词拆分
https://sexywp.com/139-word-break.htm
这里引入一个bool型的数组,用于记录我们之前已经在哪个位置进行过切割,而且后续不行的。
要加剪枝操作,在前面遍历的时候,把结果存一下,后面再遍历到这个位置的时候,就可以直接返回结果。
class Solution(object):
def __init__(self):
self.res= False
def dfs(self,s,start,wordDict,memo):
if start==len(s):
self.res=True
if self.res==True:
return
for i in range(start,len(s)+1):
cur=s[start:i]
if cur in wordDict and memo[i]:
self.dfs(s,i,wordDict,memo)
if self.res:
memo[start]=1
else:
memo[start]=0
def wordBreak(self, s, wordDict):
"""
:type s: str
:type wordDict: List[str]
:rtype: bool
"""
memo=[1 for _ in range(len(s)+1)]
self.dfs(s,0,wordDict,memo)
if self.res:
return True
return False
638大礼包
leetcode每日一题202110_MaYingColdPlay的博客-CSDN博客
二叉树的路径总和
576. 出界的路径数
class Solution(object):
def findPaths(self, m, n, maxMove, startRow, startColumn):
"""
:type m: int
:type n: int
:type maxMove: int
:type startRow: int
:type startColumn: int
:rtype: int
"""
return self.dfs(m,n,maxMove,startRow,startColumn,{}) % (10**9+7)
def dfs(self, m, n, k, i, j, cnt):
if (k, i, j) in cnt:
return cnt[(k, i, j)]
if i < 0 or j < 0 or i == m or j == n:
return 1
if k == 0:
return 0
cnt[(k, i, j)] = 0
for d in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
ni, nj = i+d[0], j+d[1]
cnt[(k, i, j)] += self.dfs(m, n, k-1, ni, nj, cnt)
return cnt[(k, i, j)]
递归-找出不同的二进制字符串
class Solution(object):
def __init__(self):
self.res=''
def dfs(self,cur_str,n,nums):
if len(cur_str)==n:
if cur_str not in nums:
self.res=cur_str
return
self.dfs(cur_str+'1',n,nums)
self.dfs(cur_str+'0',n,nums)
def findDifferentBinaryString(self, nums):
"""
:type nums: List[str]
:rtype: str
"""
n=len(nums[0])
self.dfs('',n,nums)
return self.res
记忆化dfs
题解
看起来是dfs实际上是找规律的题
5891.找出缺失的观测数据
这是一个周赛题,我做的时候以为是类似于组合问题的dfs,一直超时,剪枝剪了半天都通不过。看了答案才知道是找规律题。因为这个数量太大,递归肯定超时,而且只让输出一个,模拟就行了。