LeetCode 17, 46, 47, 77, 78, 90, 286, 79, 93: DFS & Backtracking方法题解 (python)
描述:
DFS 是一种非常重要的算法思想,也有很多具体的应用。这里简单总结一下LeetCode中比较基础,比较典型的DFS / Backtracking法的应用。在这里试图总结出一个“通用”的模版,以及一些题需要的注意的点。
链接: 17. Letter Combinations of a Phone Number.
链接: 46. Permutations.
链接: 47. Permutations II.
链接: 77. Combinations.
链接: 78. Subsets.
链接: 90. Subsets II.
链接: 286. Walls and Gates.
链接: 79. Word Search.
链接: 93. Restore IP Addresses.
系列1: 17,46,47
17. Letter Combinations of a Phone Number.
思路:
这是典型的排列问题。一看到排列问题,就能想到backtracking/dfs法去做,这道题就可解。
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
phone = {'2': ['a', 'b', 'c'], 把电话按键对应的字典表示出来
'3': ['d', 'e', 'f'],
'4': ['g', 'h', 'i'],
'5': ['j', 'k', 'l'],
'6': ['m', 'n', 'o'],
'7': ['p', 'q', 'r', 's'],
'8': ['t', 'u', 'v'],
'9': ['w', 'x', 'y', 'z']}
self.result = []
if not digits:
return self.result
self.dfs(digits, phone, 0, '')
return self.result
def dfs(self, digits, phone, start, res):
if len(res) == len(digits):
self.result.append(res)
return 注意,这里要用return退出函数
for i in phone[digits[start]]:
self.dfs(digits, phone, start+1, res+i) 函数的目的和递归一句话就够了。
总结:
注意函数退出条件,注意dfs的退出。其他地方都是套路。
46. Permutations
思路:
典型的全排列问题。回溯法/dfs去解即可。
DFS 解法1:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
self.result = []
self.dfs(nums, [])
return self.result
def dfs(self, nums, res):
if len(res) == len(nums):
self.result.append(res)
for i in range(len(nums)):
if nums[i] not in res:
self.dfs(nums, res+[nums[i]])
DFS 解法2:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
self.result = []
self.dfs(nums, [])
return self.result
def dfs(self, nums, res):
if not nums:
self.result.append(res)
for i in range(len(nums)):
self.dfs(nums[:i]+nums[i+1:], res+[nums[i]])
总结:
解法1是比较常规的一种思路:dfs函数的退出标志是子结果(res)的长度和nums长度相同。这很好理解。
解法2相对则稍微有那么一点不同。退出的条件是输入的数组为空时退出:因为我们在递归的时候,不断的在把i往后遍历,同时输入数组的时候会把nums[i]剔除,nums[i]会加入到res中。这样,当输入为空数组的时候,就说明每个nums[i]都被考虑了一次了,自然就可以退出函数。
这里的dfs处理和其他题目略有区别。
个人认为第一种传统一些的解法更适于这类题型的整体总结与掌握。
链接: 47. Permutations II.
思路:
这是本类题目中第一次出现需要重复值处理的情况。对于重复值,一般需要注意两点:
1)原数组排序
2)标记处理过的值
一定不能忘记排序!
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
self.result = []
visited = [False]* len(nums)
self.dfs(sorted(nums), [], visited) 一定不能忘记排序!
return self.result
def dfs(self, nums, res, seen): 需要一个seen变量,去标记已经访问过的值
if len(nums) == len(res):
self.result.append(res)
for i in range(len(nums)):
if not seen[i]:
if nums[i-1] == nums[i] and not seen[i-1] and i>0: 重复值处理的“话术”
continue
seen[i] = True nums[i]已经访问了:人人为我
self.dfs(nums, res+[nums[i]], seen)
seen[i] = False 别忘了为下一次递归还原:我为人人
总结:
需要重复值处理的时候,记住两点即可:
1)排序
2)“人人为我,我为人人”
总结:
1)看到排列问题就想到 DFS/Backtracking
2)重复值处理的两个关键点
参考链接:
链接: General Python Solution of Backtracking Questions.
链接: Recision II.