1️⃣3️⃣ 回溯
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
题解:
-
全排列的题很适合用回溯来做, 就是在n个数中按不同顺序选择数据填入, 定义递归函数bT(first,oput), oput为当前排列顺序, first标识排列到第第first位置, 如果first=n,即为当前排列结束
-
回溯重要操作是如何撤回上一步已经结束了的操作, 本题使用oput数组记录排列的顺序, 那么撤回操作也就是在oput上操作
-
class Solution { public static void backTracking(int n ,List<Integer> output, List<List<Integer>> res, int first){ if(first==n){ res.add(new ArrayList<Integer>(output)); } for(int i=first;i<n;i++){ Collections.swap(output,first,i); backTracking(n,output,res,first+1); // 动作完成, 撤销 Collections.swap(output,first,i); } } public List<List<Integer>> permute(int[] nums) { int n = nums.length; List<List<Integer>> res = new ArrayList<List<Integer>>(); List<Integer> output = new ArrayList<Integer>(); for(int num : nums){ output.add(num); } backTracking(n,output,res,0); return res; } }
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集).解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
题解:
-
首先可以和上题一样使用回溯, 并且都是数组, 回溯的条件也是差不多,
-
新建添加的数组长度可以是0,1,…~n-1,那么就是用循环遍历从长度1到长度n, 每个长度使用回溯来添加数据
-
public List<List<Integer>> subsets(int[] nums) { int n = nums.length; List<List<Integer>> res = new ArrayList<List<Integer>>(); res.add(new ArrayList<>()); for(int i=1;i<=n;i++){ List<Integer> lst = new ArrayList<>(); backTracking(nums,lst,res,i,0); } return res; } public void backTracking(int[] nums,List<Integer> lst,List<List<Integer>> res,int i, int start){ if(lst.size() == i){ res.add(new ArrayList<>(lst)); return ; } for(int idx = start;idx<nums.length;idx++){ lst.add(nums[idx]); backTracking(nums,lst,res,i,idx+1); lst.remove(lst.size()-1); } }
-
-
第二种就是, 从前往后遍历数组, 遇到一个数就把所有子集加到已存在的子集末尾成为一个新的子集
-
public List<List<Integer>> subsets(int[] nums){ int n = nums.length; List<List<Integer>> res = new ArrayList<List<Integer>>(); res.add(new ArrayList<>()); for(int i=0;i<n;i++){ int size = res.size(); for(int j = 0;j<size;j++){ List<Integer> newList = new ArrayList<>(res.get(j)); newList.add(nums[i]); res.add(newList); } } return res; }
-
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
题解:
-
逐渐发现且能够感觉到回溯是怎么一回事了, 本题前提就是建立一张号码与字母的映射表, 然后根据数字长度回溯添加某一位的元素即可
-
class Solution { char[][] alphabet = new char[][] { {'a', 'b', 'c'}, {'d', 'e', 'f'}, {'g', 'h', 'i'}, {'j', 'k', 'l'}, {'m', 'n', 'o'}, {'p', 'q', 'r', 's'}, {'t', 'u', 'v'}, {'w', 'x', 'y', 'z'} }; List<String> res = new ArrayList<String>(); public List<String> letterCombinations(String digits) { int n = digits.length(); if(n==0) return new ArrayList<String>(); backTracking(digits,new StringBuilder(),0 ); return res; } public void backTracking(String digits,StringBuilder sbr,int length){ if(length == digits.length()){ res.add(sbr.toString()); return ; } int group = Integer.valueOf(digits.charAt(length))-'2'; for(char ch : alphabet[group]){ sbr.append(ch); backTracking(digits,sbr,length+1); sbr.delete(sbr.length()-1,sbr.length()); } } }
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。 对于给定的输入,保证和为 target 的不同组合数少于 150 个。
题解:
-
回溯的条件, 这里可以重复, 那么要设置一个start整型,标识每次判断从start开始, 并且还能防止重复/相对于一直让idx从0开始遍历, 这里会有重复数据出现
-
回溯的参数还需要参与遍历的数组, 参与判断的target值, 还有当前总值sum
-
class Solution { List<List<Integer>> res = new ArrayList<List<Integer>>(); List<Integer> lst = new ArrayList<>(); public List<List<Integer>> combinationSum(int[] candidates, int target) { int n = candidates.length; backTracking(candidates,target,0,0); return res; } public void backTracking(int[] candidates,int target,int cnt,int start){ if(cnt == target){ res.add(new ArrayList<>(lst)); return ; } for(int i=start;i<candidates.length;i++){ if(cnt+candidates[i] <= target){ lst.add(candidates[i]); backTracking(candidates,target,cnt+candidates[i],i); lst.remove(lst.size() - 1); } } } }
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
题解:
-
要动态的维护是一个有效的字符组合, 只要保证左括号始终大于右括号的数量即可, 在左右括号均=n时,此时就是解
-
class Solution { List<String> res = new ArrayList<String>(); public List<String> generateParenthesis(int n) { StringBuilder sbr = new StringBuilder(); sbr.append('('); backTracking(n,sbr,1,0); return res; } public void backTracking(int n,StringBuilder sbr,int left,int right){ if(left==right&& left==n){ res.add(sbr.toString()); return ; } if(left < n){ sbr.append('('); backTracking(n,sbr,left+1,right); sbr.deleteCharAt(sbr.length()-1); } if(right<left){ sbr.append(')'); backTracking(n,sbr,left,right+1); sbr.deleteCharAt(sbr.length()-1); } } }
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
题解:
-
一开始我想的是用可变字符串存储走到相应位置的字符串与目标值比较是否starsWith,但每次比较都需要toString很影响性能,还忘了把已访问过的字符设置为已访问, 所以换个方法
-
还是采用索引存储的方法, 每次更新索引, 比较word.charAt(idx)与当前字符是否相同, 比较四个方向上的字符, 有一个为true则真 ,并且访问下一个字符前先将当前字符修改位不可见防止访问循环, 回溯时恢复即可
-
class Solution { public boolean exist(char[][] board, String word) { for(int i=0;i<board.length;i++){ for(int j=0;j<board[0].length;j++){ if(board[i][j]==word.charAt(0)){ if(backTracking(board,word,i,j,0)){ return true; } } } } return false; } public boolean backTracking(char[][] board,String word,int row,int col,int idx){ if(idx==word.length()){ return true; } if (row < 0 || row >= board.length || col < 0 || col >= board[0].length || board[row][col] != word.charAt(idx)) { return false; } char temp = board[row][col]; board[row][col] = '*'; boolean isFind = backTracking(board, word, row - 1, col, idx + 1) || backTracking(board, word, row + 1, col, idx + 1) || backTracking(board, word, row, col - 1, idx + 1) || backTracking(board, word, row, col + 1, idx + 1); board[row][col] = temp; return isFind; } }
给你一个字符串 s,请你将 s 分割成一些 子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
题解:
-
根据给出的例子来看, 可以得到几个提示,比如可以根据长度来逐渐判断, 首先是len=1,len=2,…len=n,那么如何判断回文, 搓一个reverse
-
回溯条件就是start开始拼接字符串的位置==length, 说明判断到了尾部, 此时lst里存储的都是回文字符串, add进res即可
-
class Solution { List<List<String>> res = new ArrayList<List<String>>(); List<String> lst = new ArrayList<>(); public List<List<String>> partition(String s) { backTracking(s,0); return res; } public void backTracking(String s, int start){ if(start==s.length()){ res.add(new ArrayList<>(lst)); return ; } for(int i=start;i<s.length();i++){ String sub = s.substring(start,i+1); if(isReverse(sub)){ lst.add(sub); backTracking(s,i+1); lst.remove(lst.size()-1); } } } public boolean isReverse(String s) { int left = 0, right = s.length() - 1; while (left < right) { if (s.charAt(left) != s.charAt(right)) { return false; } left++; right--; } return true; // 全部匹配,返回 true } }
按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
题解:
-
需要的操作有: 判断两个皇后是否在同一斜线上/正斜线和反斜线, 由于每一行只存在一个皇后, 可以根据n的长度维持一个queens, queens[i]即为第i个皇后所在的列; 需要一个回溯函数找出方案,; 需要一个函数来fill填充棋盘
-
回溯函数:
- 存储所有数据的res, 存储维护数据的queens,棋盘大小n, 当前大小row, 列回溯集合columns, 正反对角线集合diagnals
- 皇后放置条件: 第row行存放在第i列, 那么其他行的第i列不能有数据, 即col集合中不能有i, 正反对角线不能有两个皇后成对角线, 那么正对角线: 当前row-i的值不能存在于diagnals1中//不与其他皇后并列一行, 同理反对角线row+i 不存在于diagnals2中
- 回溯, 将col,diagnals1, diagnals2中的数据移出
-
class Solution { public List<List<String>> solveNQueens(int n) { List<List<String>> res = new ArrayList<List<String>>(); int[] queens = new int[n]; Arrays.fill(queens,-1); Set<Integer> columns = new HashSet<Integer>(); Set<Integer> diagnals1 = new HashSet<Integer>(); Set<Integer> diagnals2 = new HashSet<Integer>(); backTracking(res,queens,n,0,columns,diagnals1,diagnals2); return res; } public void backTracking(List<List<String>> res,int[] queens,int n, int row, Set<Integer> columns,Set<Integer> diagnals1,Set<Integer> diagnals2){ if(row == n){ List<String> board = generateBoard(queens,n); res.add(board); }else{ for(int i=0;i<n;i++){ if(columns.contains(i)){ continue; } int diagnal1 = row - i; int diagnal2 = row + i; if(diagnals1.contains(diagnal1)){ continue; } if(diagnals2.contains(diagnal2)){ continue; } queens[row] = i; columns.add(i); diagnals1.add(diagnal1); diagnals2.add(diagnal2); backTracking(res,queens,n,row+1,columns,diagnals1,diagnals2); queens[row] = -1; columns.remove(i); diagnals1.remove(diagnal1); diagnals2.remove(diagnal2); } } } public List<String> generateBoard(int[] queens, int n){ List<String> board = new ArrayList<String>(); for(int i=0;i<n;i++){ char[] row = new char[n]; Arrays.fill(row,'.'); row[queens[i]] = 'Q'; board.add(new String(row)); } return board; } }