解决一个回溯问题,实际上就是一个决策树的遍历过程。需要考虑三个问题
1、路径:就是已经做出的选择
2、选择列表:就是当前可以做出的选择
3、结束的条件:就是到达决策树底层,无法在做选择的条件。
算法框架:
1 2 3 4 5 6 7 8 9 result = [] def backtrack (路径, 选择列表 ): if 满足结束条件: result.add(路径) return for 选择 in 选择列表: 做选择 backtrack(路径, 选择列表) 撤销选择/回溯
这是一个全排列问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution : def letterCombinations (self, digits: str ) ->list : ch_dict = { "2" : "abc" , "3" : "def" , "4" :"ghi" , "5" : "jkl" , "6" :"mno" , "7" :"pqrs" , "8" :"tuv" , "9" :"wxyz" } results = [] for dig in digits: temp_list = [] for ch in ch_dict[dig]: temp_list.append(ch) print (temp_list) if len (results) == 0 : results = temp_list else : temp_list2 = [] for res in results: for t_ch in temp_list: temp_list2.append(res+t_ch) results = temp_list2 return results
官方解法 首先使用哈希表存储每个数字对应的所有可能的字母,然后进行回溯操作。
回溯过程中维护一个字符串 ,表示已有的字母排列(如果未遍历完电话号码的所有数字,则已有的字母排列是不完整的)。
该字符串初始为空。每次取电话号码的一位数字 ,从哈希表中获得该数字对应的所有可能的字母,并将其中的一个字母插入到已有的字母排列后 面,然后继续处理电话号码的后一位数字,直到处理完电话号码中的所有数字,即得到一个完整的字母排列。
然后进行回退操作,遍历其余的字母排列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def officeletterCombinations(self, digits: str) ->list: ch_dict = { "2": "abc", "3": "def", "4":"ghi", "5": "jkl", "6":"mno", "7":"pqrs", "8":"tuv", "9":"wxyz" } combination = list() combinations = list() def backtrack(index: int): # 这里是一个隐含的限制条件,组合的长度和输入的长度一样 if index == len(digits): combinations.append("".join(combination)) else: digit = digits[index] # 获取当前的数组 # 遍历当前数字对应的字母 for letter in ch_dict[digit]: combination.append(letter) backtrack(index+1) # 遍历下一个数字 combination.pop() backtrack(0) return combinations
官方解法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution : def generateParenthesis (self, n: int ) -> list : results = [] res = [] def backtract (left:int ,right:int ): print ("-->" ,res) if len (res) == 2 *n: results.append('' .join(res)) return print ("---->" ) if left < n: res.append("(" ) backtract(left+1 ,right) res.pop() print ("===========>" ) if right < left: res.append(")" ) backtract(left,right+1 ) res.pop() backtract(0 ,0 ) return results
参考题解
N皇后,经典回溯算法(图文详解) - N 皇后 - 力扣(LeetCode) (leetcode-cn.com)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class Solution { public List<String> construct (char [][] chess) { List<String> path = new ArrayList<>(); for (int i = 0 ; i < chess.length; i++) { path.add(new String(chess[i])); } return path; } public boolean valid (char [][]chess,int row,int col) { for (int i=0 ; i<row;i++){ if (chess[i][col] == 'Q' ) return false ; } for (int i = row-1 ,j=col+1 ;i>=0 && j<chess.length;i--,j++){ if (chess[i][j] == 'Q' ) return false ; } for (int i=row-1 ,j=col-1 ;i>=0 && j>=0 ;i--,j--){ if (chess[i][j] == 'Q' ) return false ; } return true ; } public void backtract (List<List<String>> res,char [][] chess,int row) { if (row == chess.length){ res.add(construct(chess)); return ; } for (int col=0 ; col < chess.length;col++){ if (valid(chess, row, col)){ chess[row][col] = 'Q' ; backtract(res, chess, row+1 ); chess[row][col]='.' ; } } } public List<List<String>> solveNQueens(int n) { char [][] chess = new char [n][n]; for (int i = 0 ; i < n; i++) for (int j = 0 ; j < n; j++) chess[i][j] = '.' ; List<List<String>> res = new ArrayList<>(); backtract(res, chess, 0 ); return res; }
参考用户:powcai - 力扣(LeetCode) (leetcode-cn.com)
题解参考自评论区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution : def combinationSum (self, candidates: list ( ), target: int ): res_s = list () res = list () n = len (candidates) candidates = sorted (candidates) def backtract (i ): if sum (res) == target: res_s.append(res.copy()) return for j in range (i,n): if sum (res) + candidates[j]>target: break res.append(candidates[j]) backtract(j) res.pop(-1 ) backtract(0 ) return res_s
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution : def combine (self, n: int , k: int ): ress = [] res = [] def backtract (j ): if len (res) == k: ress.append(res.copy()) return for i in range (j,n+1 ): if i in res: print (res,i) break res.append(i) backtract(j+1 ) res.pop() backtract(1 ) return ress
本题的关键在于去重,参考评论区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution : def combinationSum2 (self, candidates: list ( ), target: int ): ress = [] res = [] candidates = sorted (candidates) n = len (candidates) def backtract (i ): if sum (res) == target and res not in ress: ress.append(res.copy()) return for j in range (i,n): if j != i and candidates[j] == candidates[j-1 ]: continue if sum (res) + candidates[j] > target: break res.append(candidates[j]) backtract(j+1 ) res.pop() backtract(0 ) return ress
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution : def permute (self, nums: list ): ress = [] res = [] n = len (nums) def backtract (i ): if len (res) == n : ress.append(res.copy()) return for j in range (n): if nums[j] in res: continue res.append(nums[j]) backtract(j+1 ) res.pop() backtract(0 ) return ress
参考自评论区
树层上去重(used[i - 1] == false),的树形结构如下:
树枝上去重(used[i - 1] == true)的树型结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution : def permuteUnique (self, nums ): ress = list () res = list () n = len (nums) nums = sorted (nums) used = [0 ] * n def backtrack (): if len (res) == n: ress.append(res.copy()) return for i in range (n): if not used[i]: if i>0 and nums[i] == nums[i-1 ] and used[i-1 ] == False : continue used[i] = 1 res.append(nums[i]) backtrack() res.pop() used[i] = 0 backtrack() return ress