本文我们就Leetcode中的一个类型的题目backtracking进行一系列的总结和归纳。
backtracking这个方法本质是建立在递归的基础上,不断尝试新的路径,这里关键是每次尝试完以后需要退回来也就是回溯。
如果你已经找到了解决方案,那么返回成功
for(现在位置可以的所有可能选择){
选择其中一个方案然后沿着路径前进一步
使用递归的方法从新的位置解决问题
如果新的位置可以成功解决,向上一级返回成功
从现在位置恢复到循环之前的位置 }
如果到这里表示仍然没有成功,返回失败
下面我们来看一下的例题:
package DFS; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.Stack; public class DFSProblem { public static void main(String[] args) { // TODO Auto-generated method stub } /* * 140. Word Break II * s ="catsanddog"
,dict =["cat", "cats", "and", "sand", "dog"]
.A solution is["cats and dog", "cat sand dog"]
. * 这里聪明的点就在于,不是一个一个的加的,是一个词一个词的加的,遇到一个词,再继续往下走. *所有的可能选项就是包含在dict里面的所有的单词,而不是一个一个的字母。 * */ public ArrayList<String> wordBreak(String s, Set<String> dict) { ArrayList<String> res = new ArrayList<String>(); if (s == null || s.length() == 0) return res; helper(s, dict, 0, "", res); return res; } private void helper(String s, Set<String> dict, int start, String item,ArrayList<String> res) { if (start >= s.length()) { res.add(item); return; } StringBuilder str = new StringBuilder(); for (int i = start; i < s.length(); i++) { str.append(s.charAt(i)); if (dict.contains(str.toString())) { String newItem = item.length() > 0 ? (item + " " + str.toString()) : str.toString(); helper(s, dict, i + 1, newItem, res); } }} /* * Number of Islands 12.19 by Mingyang * 直接设一个叫count的值,没遇到一个1,就把所有相连的1全部变为0,这样,到底遇到几次1,就是最终有几个小岛啦 */ public int numIslands(char[][] grid) { if (grid == null || grid.length == 0 || grid[0].length == 0) return 0; int count = 0; for (int i = 0; i < grid.length; i++) { for (int j = 0; j < grid[0].length; j++) { if (grid[i][j] == ‘1‘) { count++; dfs(grid, i, j); } } } return count; } public void dfs(char[][] grid, int i, int j) { // validity checking if (i < 0 || j < 0 || i > grid.length - 1 || j > grid[0].length - 1) return; // if current cell is water or visited if (grid[i][j] != ‘1‘) return; // set visited cell to ‘0‘ grid[i][j] = ‘0‘; // merge all adjacent land dfs(grid, i - 1, j); dfs(grid, i + 1, j); dfs(grid, i, j - 1); dfs(grid, i, j + 1); } /* * Palindrome Partitioning 12.17 by Mingyang Return all possible palindrome * partitioning of s. */ public List<List<String>> partition(String s) { List<String> item = new ArrayList<String>(); List<List<String>> res = new ArrayList<List<String>>(); if (s == null || s.length() == 0) return res; dfs(s, 0, item, res); return res; } public void dfs(String s, int start, List<String> item, List<List<String>> res) { if (start == s.length()) { res.add(new ArrayList<String>(item)); return; } for (int i = start; i < s.length(); i++) { String str = s.substring(start, i + 1);// 每一轮dfs进来都是先取第一个数,start // index if (isPalindrome(str)) { item.add(str); dfs(s, i + 1, item, res);// 上面取到i,所以下一个start index就是i+1 item.remove(item.size() - 1); } } } // 也可以再少用一个变量 public void dfs(String s, List<String> temp, List<List<String>> res) { if (s.length() == 0) { res.add(new ArrayList<String>(temp)); return; } int len = s.length(); for (int i = 1; i < len; i++) { String subs = s.substring(0, i); if (isPalindrome(subs)) { temp.add(subs); String resub = s.substring(i); dfs(resub, temp, res); temp.remove(temp.size() - 1); } } } public boolean isPalindrome(String s) { int low = 0; int high = s.length() - 1; while (low < high) { if (s.charAt(low) != s.charAt(high)) return false; low++; high--; } return true; } /* * 93. Restore IP Addresses 12.16 by Mingyang * 这里不用StringBuffer因为我们最后要检查每一小块string到底是否符合要求 */ public static List<String> restoreIpAddresses1(String s) { List<String> res = new ArrayList<String>(); dfs(res, s, 0, "", 4); return res; } public static void dfs(List<String> res, String s, int start, String item, int count) { if (start == s.length() && count == 0) { System.out.println("Succ" + item); res.add(item); return; } if (count <= 0) { System.out.println("meet 0" + item + count); return; } StringBuffer sb = new StringBuffer(); for (int i = start; i < s.length(); i++) { sb.append(s.charAt(i)); if (isValid(sb.toString())) { dfs(res, s, i + 1, item.isEmpty() ? (sb.toString()) : (item + ‘.‘ + sb.toString()), --count); count++; System.out.println("return" + item + ":" + count); } else { return;// 为什么需要写这个呢,因为这个可以防止多余的计算,比如数字太大2552551113,会让下面的isValid失效,就是省去多余的 } } } public static List<String> restoreIpAddresses(String s) { List<String> res = new ArrayList<String>(); String item = new String(); if (s.length() < 4 || s.length() > 12) return res; dfs(s, 0, item, res); return res; } public static void dfs(String s, int start, String item, List<String> res) { if (start == 3 && isValid(s)) { res.add(item + s); return; } for (int i = 0; i < 3 && i < s.length() - 1; i++) { // 注意不要出界啊,否则不好玩了 String substr = s.substring(0, i + 1); if (isValid(substr)) dfs(s.substring(i + 1, s.length()), start + 1, item + substr + ‘.‘, res); } } public static boolean isValid(String s) { if (s.charAt(0) == ‘0‘) return s.equals("0"); // 如果以0开头的,必须要检查是否等于001,011等不合格的,若开头为0,整个数必须为0 System.out.println(s); int num = Integer.parseInt(s); if (num <= 255 && num > 0) return true; else return false; } /* * 212. Word Search II 12.17 by Mingyang 这么做可以在word * search的基础之上做,但是效率不高,所以后面可以用tire tree */ public List<String> findWords(char[][] board, String[] words) { ArrayList<String> result = new ArrayList<String>(); int m = board.length; int n = board[0].length; for (String word : words) { boolean flag = false; boolean[][] visited = new boolean[m][n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { char[][] newBoard = new char[m][n]; for (int x = 0; x < m; x++) for (int y = 0; y < n; y++) newBoard[x][y] = board[x][y]; if (dfs4(newBoard, word, i, j, 0, visited)) { flag = true; } } } if (flag) { result.add(word); } } return result; } /* * 79. Word Search 12.16 by Mingyang 这里的起点就是遍历所有的点,找出这个起点 * 我自己做的时候,用了一个StringBuffer来进行加减,判断条件就是sb和word相等,但是!每次加减sb造成了时间上的浪费 * 这里我们用一个index来就好了,每次完了以后,index+1.这样的话我们就可以通过index和word的长度来判断 * 另外有一个技巧就是index不用自己加加,只用加1,这样不会改变index的本质,不用退回来再减一个了。 */ public boolean exist(char[][] board, String word) { int m = board.length; int n = board[0].length; boolean[][] visited = new boolean[m][n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (dfs4(board, word, 0, i, j, visited)) return true; } } return false; } public boolean dfs4(char[][] board, String word, int index, int rowindex, int colindex, boolean[][] visited) { if (index == word.length()) return true; if (rowindex < 0 || colindex < 0 || rowindex >= board.length || colindex >= board[0].length) return false; if (visited[rowindex][colindex]) return false; if (board[rowindex][colindex] != word.charAt(index)) return false; visited[rowindex][colindex] = true; boolean res = dfs4(board, word, index + 1, rowindex - 1, colindex, visited) || dfs4(board, word, index + 1, rowindex + 1, colindex, visited) || dfs4(board, word, index + 1, rowindex, colindex + 1, visited) || dfs4(board, word, index + 1, rowindex, colindex - 1, visited); visited[rowindex][colindex] = false; return res; } /* * 90. Subsets II 12.16 by Mingyang * 这里我们将后面重复的部分算进来,就是当有重复出现的时候,结果不能重复,我就加了一个boolean array */ public static List<List<Integer>> subsetsWithDup(int[] nums) { List<List<Integer>> res = new ArrayList<List<Integer>>(); ArrayList<Integer> item = new ArrayList<Integer>(); if (nums.length == 0 || nums == null) return res; Arrays.sort(nums); boolean[] array = new boolean[nums.length]; for (int len = 1; len <= nums.length; len++) dfs3(nums, 0, len, item, res, array); res.add(new ArrayList<Integer>()); return res; } public static void dfs3(int[] S, int start, int len, List<Integer> item, List<List<Integer>> res, boolean[] array) { if (item.size() == len) { res.add(new ArrayList<Integer>(item)); return; } for (int i = start; i < S.length; i++) { if (i != 0 && S[i] == S[i - 1] && array[i - 1] == false) continue; item.add(S[i]); array[i] = true; dfs3(S, i + 1, len, item, res, array); item.remove(item.size() - 1); array[i] = false; } } /* * 78. Subsets 12.16 by Mingyang * 注意这里虽然感觉很像combine,但是这个array可以随意的,可以为任意数的array,所以还是得用array来 * ,并且那个array要sort一下,记住,千万不要忘了加空集 */ public static List<List<Integer>> subsets(int[] nums) { List<List<Integer>> res = new ArrayList<List<Integer>>(); ArrayList<Integer> item = new ArrayList<Integer>(); if (nums.length == 0 || nums == null) return res; Arrays.sort(nums); for (int len = 1; len <= nums.length; len++) dfs3(nums, 0, len, item, res); res.add(new ArrayList<Integer>()); return res; } public static void dfs3(int[] S, int start, int len, List<Integer> item, List<List<Integer>> res) { if (item.size() == len) { res.add(new ArrayList<Integer>(item)); return; } for (int i = start; i < S.length; i++) { item.add(S[i]); dfs3(S, i + 1, len, item, res); item.remove(item.size() - 1); } } /* * 77. Combinations 12.16 by Mingyang * 这里有个一个start的多的参数以后就可以保证所有序列按顺序输出的,并且不会重复 12-13-14-23-24-34 */ public static List<List<Integer>> combine(int n, int k) { List<List<Integer>> res = new ArrayList<List<Integer>>(); if (n <= 0 || n < k) return res; List<Integer> item = new ArrayList<Integer>(); dfs2(n, k, 1, item, res);// because it need to begin from 1-------从1开始的哦 return res; } private static void dfs2(int n, int k, int start, List<Integer> item, List<List<Integer>> res) { if (item.size() == k) { res.add(new ArrayList<Integer>(item));// because item is // ArrayList<T> so it will // not disappear from stack // to stack return; } for (int i = start; i <= n; i++) { // 这里多加了一个start,这样就不用再判断重复了 item.add(i); dfs2(n, k, i + 1, item, res); item.remove(item.size() - 1); } } /* * Permutations II 12.13 by Mingyang 唯一的区别就是在这个题目中元素集合可以出现重复。 * 这给我们带来一个问题就是如果不对重复元素加以区别, 那么类似于{1,1,2}这样的例子我们会有重复结果出现。那么如何避免这种重复呢? * 方法就是对于重复的元素循环时跳过递归函数的调用,只对第一个未被使用的进行递归, * 我们那么这一次结果会出现在第一个的递归函数结果中,而后面重复的会被略过。 如果第一个重复元素前面的元素还没在当前结果中,那么我们不需要进行递归。 * 首先我们要对元素集合排序,从而让重复元素相邻,接下来就是一行代码对于重复元素和前面元素使用情况的判断即可。 * 这样的解法是带有一般性的,把这个代码放到Permutations中也是正确的,所以如果熟悉的话, * 面试时如果碰到这个题目建议直接实现这个代码,不要假设元素没有重复,当然可以跟面试官讨论,不过一般来说都是要考虑这个情况的哈。 */ public List<List<Integer>> permuteUnique(int[] nums) { List<List<Integer>> res = new ArrayList<List<Integer>>(); if (nums == null || nums.length == 0) return res; Arrays.sort(nums); boolean[] visited = new boolean[nums.length]; List<Integer> temp = new ArrayList<Integer>(); dfs4(nums, visited, temp, res); return res; } private void dfs4(int[] nums, boolean[] visited, List<Integer> temp, List<List<Integer>> res) { if (temp.size() == nums.length) { res.add(new ArrayList<Integer>(temp)); return; } for (int i = 0; i < nums.length; i++) { if (i > 0 && !visited[i - 1] && nums[i] == nums[i - 1]) // 这个非常的重要!!!!!!!!!! continue; if (!visited[i]) { visited[i] = true; temp.add(nums[i]); dfs4(nums, visited, temp, res); temp.remove(temp.size() - 1); visited[i] = false; } } } /* * 46. Permutations 12.13 by Mingyang 方法还是原来那个套路,还是用一个循环递归处理子问题。 * 区别是这里并不是一直往后推进的,前面的数有可能放到后面,所以我们需要维护一个visited数组来表示该元素是否已经在当前结果中, * 因为每次我们取一个元素放入结果,然后递归剩下的元素,所以不会出现重复 * 这道题还有一个扩展就是如果元素集合中会出现重复,那么意味着我们需要跳过一些重复元素 */ public List<List<Integer>> permute(int[] nums) { List<List<Integer>> res = new ArrayList<List<Integer>>(); if (nums == null || nums.length == 0) return res; boolean[] visited = new boolean[nums.length]; List<Integer> temp = new ArrayList<Integer>(); dfs3(nums, visited, temp, res); return res; } private void dfs3(int[] nums, boolean[] visited, List<Integer> temp, List<List<Integer>> res) { if (temp.size() == nums.length) { res.add(new ArrayList<Integer>(temp)); return; } for (int i = 0; i < nums.length; i++) { if (!visited[i]) { visited[i] = true; temp.add(nums[i]); dfs3(nums, visited, temp, res); temp.remove(temp.size() - 1); visited[i] = false; } } } /* * combinationSum III 12.18 by Mingyang * 两个地方需要注意,第一个是++start,因为你进入下一个dfs的时候,需要考虑加一个 * ,但是如果你是start++就会先传递start进去后面再加,这样就不行 * 另外一个就是i一定要取到9,虽然大小聪明,想只取到7,但是后面的遍历可能也会遍历到9啊。 */ public List<List<Integer>> combinationSum3(int k, int n) { List<List<Integer>> res = new ArrayList<List<Integer>>(); List<Integer> temp = new ArrayList<Integer>(); dfs(res, temp, k, n, 1); return res; } public void dfs(List<List<Integer>> res, List<Integer> temp, int k, int n, int start) { if (k == 0) { if (n == 0) { res.add(new ArrayList<Integer>(temp)); } return; } for (int i = start; i <= 9; i++) { temp.add(i); dfs(res, temp, k - 1, n - i, ++start); temp.remove(temp.size() - 1); } } /* * Combination Sum II 12.13 by Mingyang Each number in C may only be used * once in the combination. * 在这里我们还是需要在每一次for循环前做一次判断,因为虽然一个元素不可以重复使用,但是如果这个元素重复出现是允许的, * 但是为了避免出现重复的结果集,我们只对于第一次得到这个数进行递归,接下来就跳过这个元素了, * 因为接下来的情况会在上一层的递归函数被考虑到,这样就可以避免重复元素的出现。 Each number in C may only be used * once in the combination. I 是The same repeated number may be chosen from C * unlimited number of times. */ public List<List<Integer>> combinationSum2(int[] candidates, int target) { List<List<Integer>> res = new ArrayList<List<Integer>>(); List<Integer> temp = new ArrayList<Integer>(); if (candidates == null || candidates.length == 0) return res; Arrays.sort(candidates); dfs2(candidates, target, 0, temp, res); return res; } public void dfs2(int[] candidates, int remain, int begin, List<Integer> temp, List<List<Integer>> res) { if (remain == 0) { res.add(new ArrayList<Integer>(temp)); return; } if (remain < 0) return; for (int i = begin; i < candidates.length; i++) { if (i > begin && candidates[i] == candidates[i - 1]) continue; temp.add(candidates[i]); dfs2(candidates, remain - candidates[i], i + 1, temp, res); temp.remove(temp.size() - 1); } } /* * Combination Sum 12.13 by Mingyang The same repeated number may be chosen * from C unlimited number of times. * 注意在实现中for循环中第一步有一个判断,那个是为了去除重复元素产生重复结果的影响 * 因为在这里每个数可以重复使用,所以重复的元素也就没有作用了,所以应该跳过那层递归。 */ public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> res = new ArrayList<List<Integer>>(); List<Integer> temp = new ArrayList<Integer>(); if (candidates == null || candidates.length == 0) return res; Arrays.sort(candidates); dfs(candidates, target, 0, temp, res); return res; } public void dfs(int[] candidates, int remain, int begin, List<Integer> temp, List<List<Integer>> res) { if (remain == 0) { res.add(new ArrayList<Integer>(temp)); return; } if (remain < 0) return; for (int i = begin; i < candidates.length; i++) { if (i > 0 && candidates[i] == candidates[i - 1]) // 这里就是跳过那个重复的元素,因为每次已经可以重复使用自己了 continue; temp.add(candidates[i]); dfs(candidates, remain - candidates[i], i, temp, res); temp.remove(temp.size() - 1); } } /* * 22. Generate Parentheses 12.13 by Mingyang */ public static List<String> generateParenthesis(int n) { List<String> res = new ArrayList<String>(); if (n <= 0) return res; StringBuffer sb = new StringBuffer(); dfs100(n, n, res, sb); return res; } public static void dfs100(int left, int right, List<String> res, StringBuffer sb) { if (left == 0 && right == 0) { res.add(sb.toString()); System.out.println(sb.toString()); return; } if (left > right) return; if (left < 0 || right < 0) return; sb.append(‘(‘); dfs100(left - 1, right, res, sb); sb.deleteCharAt(sb.length() - 1); sb.append(‘)‘); dfs100(left, right - 1, res, sb); sb.deleteCharAt(sb.length() - 1); } /* * Simplify Path 12.3 by Mingyang 当遇到“/../"则需要返回上级目录,需检查上级目录是否为空。 * * 当遇到"/./"则表示是本级目录,无需做任何特殊操作。 * * 当遇到"//"则表示是本级目录,无需做任何操作。 * * 当遇到其他字符则表示是文件夹名,无需简化。 * * 当字符串是空或者遇到”/../”,则需要返回一个"/"。 * * 当遇见"/a//b",则需要简化为"/a/b"。 所以我们这里遇到简化的时候,先split为//之间的值。然后用两个堆栈来解决,因为方向是反的 * 当字符串为空或者为".",不做任何操作。 * * 当字符串不为"..",则将字符串入栈。 * * 当字符串为"..", 则弹栈(返回上级目录)。 */ public String simplifyPath(String path) { if (path == null || path.length() == 0) return path; Stack<String> stack = new Stack<String>(); String[] list = path.split("/"); for (int i = 0; i < list.length; i++) { if (list[i].equals(".") || list[i].length() == 0) continue; else if (!list[i].equals("..")) stack.push(list[i]); else { if (!stack.isEmpty()) stack.pop(); } } StringBuilder res = new StringBuilder(); Stack<String> temp = new Stack<String>(); while (!stack.isEmpty()) temp.push(stack.pop()); while (!temp.isEmpty()) res.append("/" + temp.pop()); if (res.length() == 0) res.append("/"); return res.toString(); } /* * 17. Letter Combinations of a Phone Number * 11.30 by Mingyang 是对keyboard的循环 */ public List<String> letterCombinations(String digits) { List<String> result = new ArrayList<String>(); if (digits == null || digits.length() == 0) return result; String[] keyboard = { "", "", "abc", "def", "ghi", "jkl", "mno","pqrs", "tuv", "wxyz" }; StringBuilder current = new StringBuilder(); int index = 0; dfs(digits, index, current, keyboard, result); return result; } private void dfs(String digits, int index, StringBuilder current,String[] keyboard, List<String> result) { if (index == digits.length()) { result.add(current.toString()); return; } int num = digits.charAt(index) - ‘0‘;// get integer number for (int i = 0; i < keyboard[num].length(); i++) { current.append(keyboard[num].charAt(i)); dfs(digits, index + 1, current, keyboard, result); current.deleteCharAt(current.length() - 1); } } /* * Additive Number 1.2 by Mingyang */ public boolean isAdditiveNumber(String s) { int n = s.length(); for (int i = 1; i < n; i++) { for (int j = i + 1; j < n; j++) { long a = parse(s.substring(0, i)); long b = parse(s.substring(i, j)); if (a == -1 || b == -1) continue; if (dfs(s.substring(j), a, b)) return true; } } return false; } boolean dfs(String s, long a, long b) { if (s.length() == 0) return true; for (int i = 1; i <= s.length(); i++) { long c = parse(s.substring(0, i)); if (c == -1) continue; if (c - a == b && dfs(s.substring(i), b, c)) { return true; } } return false; } long parse(String s) { if (!s.equals("0") && s.startsWith("0")) return -1; long result = 0; try { result = Long.parseLong(s); } catch (NumberFormatException e) { return -1; } return result; } }
时间: 2024-11-13 20:25:24