回溯算法和深度优先遍历
「回溯算法」与「深度优先遍历」都有「不撞南墙不回头」的意思。我个人的理解是:「回溯算法」强调了「深度优先遍历」思想的用途,用一个 不断变化 的变量,在尝试各种可能的过程中,搜索需要的结果。强调了 回退 操作对于搜索的合理性。而「深度优先遍历」强调一种遍历的思想,与之对应的遍历思想是「广度优先遍历」
回溯算法用于搜索一个问题的所有的解,用到了DFS的思想
回溯和动态规划:
DP只需要知道最优解是什么,不关心怎么来的
回溯要列举出所有的可能性
排列问题
按顺序枚举每一位可能出现的情况,已经选择的数字在 当前 要选择的数字中不能出现。按照这种策略搜索就能够做到 不重不漏
回溯和普通的DFS的区别在于,有回头的过程,且回头的时候需要把当前已经访问的节点状态重置
- 按引用传递状态
- 所有的状态修改在递归完成后回改
46. 全排列
https://leetcode-cn.com/problems/permutations/
# res = []# def backtracking(nums, tmp):# if not nums:# res.append(tmp)# return# for i in range(len(nums)):# backtracking(nums[:i]+nums[i+1:], tmp+[nums[i]])# backtracking(nums,[])# return resdepth = len(nums)visited = [False for _ in range(depth)]res = []def backtracking(temp_list, height):if height == depth:res.append(temp_list)returnfor i in range(depth):if not visited[i]:visited[i] = Truebacktracking(temp_list+[nums[i]],height+1)visited[i] = Falsebacktracking([],0)return res
47. 全排列 II
https://leetcode-cn.com/problems/permutations-ii/
主要是去重的判断条件.
- 排序, 保证相同的数字都相邻
不选择的情况有:
- 当前元素已经visited
i>0 且当前元素等于前一个元素 且 前一个元素已经被使用.
class Solution:def permuteUnique(self, nums: List[int]) -> List[List[int]]:n = len(nums)if n <2:return [nums]res = []nums.sort()visited = [False for _ in range(n)]def backtracking(temp_list, depth):if depth==n:res.append(temp_list)for i in range(n):if visited[i] or (i>0 and nums[i]==nums[i-1] and not visited[i-1]):continuevisited[i]=Truebacktracking(temp_list+[nums[i]], depth+1)visited[i]=Falsebacktracking([],0)return res
77. 组合
class Solution:def combine(self, n: int, k: int) -> List[List[int]]:res = []if k>n:return resif k == 0:return [res]def backtracking(nums,temp_list, index):if len(temp_list)==k:res.append(temp_list[:])returnfor i in range(index,n):temp_list.append(nums[i])backtracking(nums[index:],temp_list, i+1)temp_list.pop()nums = [i for i in range(1,n+1)]backtracking(nums,[],0)return res
39. 组合总和
https://leetcode-cn.com/problems/combination-sum/
什么时候使用 used/visited数组,什么时候使用 begin 变量
- 排列问题: 讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为不同列表时,需要记录哪些数字已经使用过,此时用 used 数组;
- 组合问题:不讲究顺序(即 [2, 2, 3] 与 [2, 3, 2] 视为相同列表时,需要按照某种顺序搜索,此时使用 begin 变量。
注意:具体问题应该具体分析, 理解算法的设计思想 是至关重要的,请不要死记硬背。
class Solution:def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:candidates.sort()res = []length = len(candidates)def backtracking(temp_list, index, target):if target==0:res.append(temp_list[:])returnfor i in range(index, length):residue = target - candidates[i]if residue < 0:breakbacktracking(temp_list+[candidates[i]], i, residue)backtracking([],0,target)return res
40. 组合总和 II
https://leetcode-cn.com/problems/combination-sum-ii/
这道题的核心相比上一题在于去重,也就是代码备注的部分
https://leetcode-cn.com/problems/combination-sum-ii/solution/hui-su-suan-fa-jian-zhi-python-dai-ma-java-dai-m-3/
参考高赞评论
class Solution:def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:size = len(candidates)res = []if size<1:return []def backtracking(temp_list, begin, target):if target == 0:res.append(temp_list[:])returnfor i in range(begin,size):residue = target - candidates[i]if residue<0:break#去重的核心在于这个判断# i>begin是为了保证同一层的第一个和上一级相同的数是允许的# i == i-1 是说同一层第二个及以后相同的数是不允许的if i > begin and candidates[i]==candidates[i-1]:continuebacktracking(temp_list+[candidates[i]],i+1, residue)candidates.sort()backtracking([],0,target)return res
17. 电话号码的字母组合
https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/
难得被回溯虐了一天半之后, 自己调试出来一道回溯的题
class Solution:def letterCombinations(self, digits: str) -> List[str]:lookup = {'2':'abc','3':'def','4':'ghi','5':'jkl','6':'mno','7':'pqrs','8':'tuv','9':'wxyz'}size = len(digits)res = []if size==0:return []def backtracking(temp_list, index):if len(temp_list) == size:res.append("".join(temp_list[:]))# 不要试图去构建递归的整个过程,理解两层循环所对应的不同的树的层级for i in range(index,size): # 这一层循环对应每个数字panel = lookup[digits[i]] # 每个数字对应3 or 4个字母for j in range(len(panel)): # 这个字母里选一个backtracking(temp_list+[panel[j]],i+1)backtracking([],0)return res
79. 单词搜索
https://leetcode-cn.com/problems/word-search/
一开始我按照岛屿问题的方法写,发现返回不到想要的结果。
line16标记,line22退回, 因为一个矩阵里,可能有多个相同的字母,有时候从一个位置出发不行,但从另一个位置出发是可以的
class Solution:def exist(self, board: List[List[str]], word: str) -> bool:if not board:return Falserows = len(board)cols = len(board[0])DIRECTION = ((1,0),(-1,0),(0,1),(0,-1))visited=[[False for _ in range(cols)] for _ in range(rows)]def inArea(x,y):return x>=0 and y>=0 and x<rows and y<colsdef dfs(index,x,y):if index == len(word)-1:return board[x][y]==word[index]if board[x][y]==word[index]:visited[x][y]=Truefor k in range(4):new_x = x + DIRECTION[k][0]new_y = y + DIRECTION[k][1]if inArea(new_x,new_y) and not visited[new_x][new_y] and dfs(index+1,new_x,new_y):return Truevisited[x][y]=Falsefor i in range(rows):for j in range(cols):if dfs(0,i,j):return Truereturn False
