回溯
剪枝挺有必要的,时间快了10倍多。
class Solution { private List<List<Integer>> ans = new ArrayList<>(); private List<Integer> oneans = new ArrayList<>(); public List<List<Integer>> combine(int n, int k) { flashback(n, k, 1); return ans; } private void flashback(int n, int k, int index) { if (oneans.size() == k) { ans.add(new ArrayList<>(oneans)); //这里要特别注意 return; } for (int i = index; i <= n-(k-oneans.size())+1; i++) { oneans.add(i); flashback(n, k, i+1); oneans.remove(oneans.size()-1); } } }
感觉还是二叉树的简单题更难,这直接套模板。
class Solution { private List<List<Integer>> ans = new ArrayList<>(); private List<Integer> oneans = new ArrayList<>(); public List<List<Integer>> combinationSum3(int k, int n) { flashback(k, n, 1); return ans; } private void flashback(int k, int n, int index) { if (n == 0 && oneans.size() == k) { ans.add(new ArrayList<>(oneans)); return; } for (int i = index; i <= 9 && n >= 0; i++) { oneans.add(i); flashback(k, n-i, i+1); oneans.remove(oneans.size()-1); } } }
StringBuilder的各种操作和构造map才是难点吧。StringBuilder的append放在递归里是不行的。具体原因自己百度。
class Solution { private List<String> ans = new ArrayList<>(); private StringBuilder oneans = new StringBuilder(); private String[] map = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; public List<String> letterCombinations(String digits) { if (digits == null || digits.length() == 0) return ans; flashback(digits, 0); return ans; } private void flashback(String digits, int index) { if (index == digits.length()) { ans.add(oneans.toString()); return; } char digit = digits.charAt(index); String key = map[digit-'0'-2]; for (int i = 0; i < key.length(); i++) { oneans.append(key.charAt(i)); flashback(digits, index+1); oneans.deleteCharAt(oneans.length()-1); } } }
这是一个模板题
class Solution { private List<Integer> oneans = new ArrayList<>(); private List<List<Integer>> ans = new ArrayList<>(); public List<List<Integer>> combinationSum(int[] candidates, int target) { flashback(target, candidates, 0); Arrays.sort(candidates); return ans; } private void flashback(int sum, int[] candidates, int index){ if (sum == 0) { ans.add(new ArrayList<>(oneans)); return; } if (sum < 0) return; for (int i = index; i < candidates.length; i++) { oneans.add(candidates[i]); //因为下一层也可以包含本身,所以递归是i不是i+1 flashback(sum-candidates[i], candidates, i); oneans.remove(oneans.size()-1); } } }
class Solution { private List<Integer> oneans = new ArrayList<>(); private List<List<Integer>> ans = new ArrayList<>(); public List<List<Integer>> combinationSum(int[] candidates, int target) { flashback(target, candidates, 0); Arrays.sort(candidates); return ans; } private void flashback(int sum, int[] candidates, int index){ if (sum == 0) { ans.add(new ArrayList<>(oneans)); return; } if (sum < 0 || index == candidates.length) return; oneans.add(candidates[index]); flashback(sum-candidates[index], candidates, index); oneans.remove(oneans.size()-1); flashback(sum, candidates, index+1); } }
遇事不决就画图,剪枝思路和三数之和一样。
class Solution { private List<List<Integer>> ans = new ArrayList<>(); private List<Integer> oneans = new ArrayList<>(); public List<List<Integer>> combinationSum2(int[] candidates, int target) { Arrays.sort(candidates); flashback(candidates, 0, target); return ans; } private void flashback(int[] candidates, int index, int sum) { if (sum == 0) { ans.add(new ArrayList<>(oneans)); return; } for (int i = index; i < candidates.length; i++) { if (i != index && candidates[i] == candidates[i-1]) continue; if (sum-candidates[i] < 0) break; oneans.add(candidates[i]); flashback(candidates, i+1, sum-candidates[i]); oneans.remove(oneans.size()-1); } } }
就这,就这?难点是substring记不住吧?
class Solution { private List<List<String>> ans = new ArrayList<>(); private List<String> oneans = new ArrayList<>(); public List<List<String>> partition(String s) { flashback(s, 0); return ans; } private void flashback(String s, int index) { if (index == s.length()) { ans.add(new ArrayList<>(oneans)); } for (int i = index; i < s.length(); i++) { String sb = s.substring(index, i+1); if(check(sb)) { oneans.add(sb); flashback(s, i+1); oneans.remove(oneans.size()-1); } } } private boolean check(String s){ int i = 0, j = s.length()-1; while (i < j) { if (s.charAt(i++) != s.charAt(j--)) { return false; } } return true; } }
复原IP地址 题目 解析
难点是各种边界,我在剪枝里判定了一部分边界。试了一下不剪枝,倒在了黎明的前夕(最后一个用例超时)。
class Solution { private List<String> ans = new ArrayList<>(); private List<String> oneans = new ArrayList<>(); public List<String> restoreIpAddresses(String s) { flashback(s, 0); return ans; } private void flashback(String s, int index) { int n = s.length(); if (oneans.size() == 3) { String test = s.substring(index, n); if (check(test)) { StringBuilder sb = new StringBuilder(); for (String st : oneans) { sb.append(st); sb.append('.'); } sb.append(test); ans.add(sb.toString()); } return; } for (int i = index+1; i <= index + 3 && i <= n; i++) { int m = n - i; if (m > 3*(3-oneans.size())) continue; if (m < 3-oneans.size()) break; String test = s.substring(index, i); if (check(test)) { oneans.add(test); flashback(s, i); oneans.remove(oneans.size()-1); } } } private boolean check(String s) { if (s.length() > 1 && s.charAt(0) == '0') return false; int n = 0; for (char c : s.toCharArray()) { n = n * 10 + (c - '0'); } if (n > 255) return false; return true; } }
模板题
这是我写的,求长度为1的子集,为2的子集...这样循环求到n。对于每个长度用一次回溯。
class Solution { private List<List<Integer>> ans = new ArrayList<>(); private List<Integer> oneans = new ArrayList<>(); public List<List<Integer>> subsets(int[] nums) { for (int i = 0; i <= nums.length; i++) { flashback(nums, i, 0); } return ans; } private void flashback(int[] nums, int len, int index) { if (oneans.size() == len) { ans.add(new ArrayList<>(oneans)); return; } for (int i = index; i < nums.length; i++) { oneans.add(nums[i]); flashback(nums, len, i+1); oneans.remove(oneans.size()-1); } } }
其实画个图就很清晰了,子集不是记录每个叶子结点,而是每个节点都要记录!
class Solution { private List<List<Integer>> ans = new ArrayList<>(); private List<Integer> oneans = new ArrayList<>(); public List<List<Integer>> subsets(int[] nums) { flashback(nums, 0); return ans; } private void flashback(int[] nums, int index) { ans.add(new ArrayList<>(oneans)); for (int i = index; i < nums.length; i++) { //判断结束回溯的条件其实就在循环里 oneans.add(nums[i]); flashback(nums, i+1); oneans.remove(oneans.size()-1); } } }
一开始我的去重复判断是 i != 0,这是不对的,这样就一直是在第一层去重。在当前层去重应该是 i != index。
class Solution { private List<List<Integer>> ans = new ArrayList<>(); private List<Integer> oneans = new ArrayList<>(); public List<List<Integer>> subsetsWithDup(int[] nums) { Arrays.sort(nums); flashback(nums, 0); return ans; } private void flashback(int[] nums, int index) { ans.add(new ArrayList<>(oneans)); for (int i = index; i < nums.length; i++) { if (i != index && nums[i] == nums[i-1]) continue; //去重 oneans.add(nums[i]); flashback(nums, i+1); oneans.remove(oneans.size()-1); } } }
递增子序列 题目 解析
重点就是剪枝
class Solution { private List<List<Integer>> ans = new ArrayList<>(); private List<Integer> oneans = new ArrayList<>(); public List<List<Integer>> findSubsequences(int[] nums) { flashback(nums, 0); return ans; } private void flashback(int[] nums, int index) { if (oneans.size() >= 2) { ans.add(new ArrayList<>(oneans)); } Set<Integer> map = new HashSet<>(); for (int i = index; i < nums.length; i++) { if (map.contains(nums[i])) continue; if (oneans.size() > 0 && oneans.get(oneans.size()-1) > nums[i]) continue; map.add(nums[i]); oneans.add(nums[i]); flashback(nums, i+1); oneans.remove(oneans.size()-1); } } }
一开始用set去重的,其实根本没必要。用一个boolean数组就好了。用set更加耗时。
class Solution { private List<List<Integer>> ans = new ArrayList<>(); private List<Integer> oneans = new ArrayList<>(); public List<List<Integer>> permute(int[] nums) { boolean[] used = new boolean[nums.length]; flashback(nums, used); return ans; } private void flashback(int[] nums, boolean[] used) { if (oneans.size() == nums.length) { ans.add(new ArrayList<>(oneans)); return; //这时的return是必要的,因为下面循环里没有递归的终止条件 } for (int i = 0; i < nums.length; i++) { if (used[i]) continue; oneans.add(nums[i]); used[i] = true; flashback(nums, used); oneans.remove(oneans.size()-1); used[i] = false; } } }
重新安排行程 题目
太恐怖了,先埋坑。打破了我回溯不看题解的快乐,这是看了题解也搞不懂。
难点在于判断条件和输出格式。这里用一维数组存储存储位置。下标是行数,数据是列数。
class Solution { private List<List<String>> ans = new ArrayList<>(); public List<List<String>> solveNQueens(int n) { int[] mark = new int[n]; flashback(mark, 0, n); return ans; } private void flashback(int[] mark, int index, int n) { if (index == n) { List<String> oneans = new ArrayList<>(); for (int i = 0; i < n; i++) { StringBuilder sb = new StringBuilder(); for (int j = 0; j < n; j++) { if (j == mark[i]) { sb.append('Q'); } else { sb.append('.'); } } oneans.add(sb.toString()); } ans.add(oneans); return; //这个return不可省,循环里没有终止条件 } for (int i = 0; i < n; i++) { mark[index] = i; if (check(mark, index)) { flashback(mark, index+1, n); } } } private boolean check(int[] mark, int index) { for (int i = 0; i < index; i++) { for (int j = i + 1; j <= index; j++) { if (mark[i] == mark[j]) return false; if (mark[i] - i == mark[j] - j) return false; if (mark[i] + i == mark[j] + j) return false; } } return true; } }
大意了,递归里面不能用index++,都要结束了才犯这种错误是不是太晚了?
这题是个二维数组,用双层循环,也可以用转化为一维数组。因为是有一个解就返回了,所以是用boolean类型。
class Solution { public void solveSudoku(char[][] board) { flashback(board, 0); } private boolean flashback(char[][] board, int index) { if (index == 81) return true; int row = index / 9; int col = index % 9; if (board[row][col] != '.') { return flashback(board, index + 1); } else { for (char ch = '1'; ch <= '9'; ch++) { if (check(board, row, col, ch)) { board[row][col] = ch; if (flashback(board, index + 1)) return true; board[row][col] = '.'; } } } return false; } private boolean check(char[][] board, int row, int col, char val) { for (int k = 0; k < 9; k++) { if (board[k][col] == val) return false; if (board[row][k] == val) return false; int i = row / 3 * 3 + k / 3; int j = col / 3 * 3 + k % 3; if (board[i][j] == val) return false; } return true; } }
用二维,双层循环结束相当于上面的index == 81。所以最后是return true。如果9个字符都不匹配记得要return false。可以和上面两相印证。
class Solution { public void solveSudoku(char[][] board) { flashback(board); } private boolean flashback(char[][] board) { for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { if (board[i][j] != '.') continue; for (char ch = '1'; ch <='9'; ch++) { if (check(board, i, j, ch)) { board[i][j] = ch; if (flashback(board)) return true; board[i][j] = '.'; } } return false; } } return true; } private boolean check(char[][] board, int row, int col, char val) { for (int k = 0; k < 9; k++) { if (board[k][col] == val) return false; if (board[row][k] == val) return false; int i = row / 3 * 3 + k / 3; int j = col / 3 * 3 + k % 3; if (board[i][j] == val) return false; } return true; } }
回溯因为之前刻意练习了,所以这次写得格外丝滑,飞机票那个除外。最后解数独翻车了,因为我竟然在回溯函数里用了++!

浙公网安备 33010602011771号