- 回溯
- 面试题08.12.八皇后(困难)">面试题08.12.八皇后(困难)
- 37. 解数独">37. 解数独
- 17.电话号码的字母组合(中等)">17.电话号码的字母组合(中等)
- 77.组合(中等) 给n个数返回所有k个数的组合">77.组合(中等) 给n个数返回所有k个数的组合
- 78.子集(中等) 所有的组合">78.子集(中等) 所有的组合
- 90.子集II(中等)有重复数据">90.子集II(中等)有重复数据
- 46.全排列(中等) 所有排列">46.全排列(中等) 所有排列
- 47.全排列II(中等) 有重复数据">47.全排列II(中等) 有重复数据
- 39.组合总和(中等) 选出某几个数相加为给定和,无重复数据,可以使用多次,不能有重复答案">39.组合总和(中等) 选出某几个数相加为给定和,无重复数据,可以使用多次,不能有重复答案
- 40.组合总和II(中等)选出某几个数相加为给定和,有重复数据,只能使用一次,不能有重复答案">40.组合总和II(中等)选出某几个数相加为给定和,有重复数据,只能使用一次,不能有重复答案
- 216.组合总和III(中等) 选出k个数相加为给定和,没有重复数据,只能使用一次">216.组合总和III(中等) 选出k个数相加为给定和,没有重复数据,只能使用一次
- 131.分割回文串(中等)">131.分割回文串(中等)
- 93. 复原IP地址(中等)">93. 复原IP地址(中等)
- 22.括号生成(中等)">22.括号生成(中等)
回溯
面试题08.12.八皇后(困难)
回溯算法
class Solution {private List<List<String>> result = new ArrayList<>();public List<List<String>> solveNQueens(int n) {char[][] nums = new char[n][n];// 初始化for(int i = 0; i < n; i++) {nums[i] = new char[n];for(int j = 0; j < n; j++) {nums[i][j] = '.';}}backTrace(nums, 0);return result;}private void backTrace(char[][] nums, int step) {if(step == nums.length) {List<String> snapshot = new ArrayList<>();for(char[] num : nums) {snapshot.add(new String(num));}result.add(snapshot);}for(int i = 0; i < nums.length; i++) {if(isOk(nums, step, i)) {nums[step][i] = 'Q';backTrace(nums, step + 1);nums[step][i] = '.';}}}private boolean isOk(char[][] nums, int step, int idx) {int row = step;int col = idx;// 向上检测while(row > 0) {if(nums[--row][col] == 'Q') {return false;}}row = step;col = idx;// 向左上角进行检测while(row > 0 && col > 0) {if(nums[--row][--col] == 'Q') {return false;}}row = step;col = idx;// 向右上角进行检测while(row > 0 && col < nums.length - 1) {if(nums[--row][++col] == 'Q') {return false;}}return true;}}
37. 解数独
回溯
class Solution {// 记录每行 1~9 之间的某个数字是否出现过private boolean[][] rows = new boolean[9][9];// 记录每列 1~9 之间的某个数字是否出现过private boolean[][] cols = new boolean[9][9];// 记录每个 3*3 小宫格内 1~9 之间的某个数字是否出现过private boolean[][][] block = new boolean[3][3][9];// 记录空白格 “.” 的位置private List<int[]> space = new ArrayList<>();private boolean isVaild = false;public void solveSudoku(char[][] board) {// 初始化 rows、cols、block 以及 spacefor(int i = 0; i < 9; i++) {for(int j = 0; j < 9; j++) {if(board[i][j] == '.') {space.add(new int[]{i, j});} else {int idx = board[i][j] - '0' - 1;rows[i][idx] = cols[j][idx] = block[i / 3][j / 3][idx] = true;}}}backTrace(board, 0);}private void backTrace(char[][] board, int step) {if(step == space.size()) {isVaild = true;return;}int[] pos = space.get(step);int row = pos[0], col = pos[1];// !isVaild 剪枝for(int i = 0; i < 9 && !isVaild; i++) {if(!rows[row][i] && !cols[col][i] && !block[row / 3][col / 3][i]) {rows[row][i] = cols[col][i] = block[row / 3][col / 3][i] = true;board[row][col] = (char)(i + '0' + 1);backTrace(board, step + 1);rows[row][i] = cols[col][i] = block[row / 3][col / 3][i] = false;}}}}
17.电话号码的字母组合(中等)
class Solution {private List<String> result = new ArrayList<>();Map<Character, String> phoneMap = new HashMap<Character, String>() {{put('2', "abc");put('3', "def");put('4', "ghi");put('5', "jkl");put('6', "mno");put('7', "pqrs");put('8', "tuv");put('9', "wxyz");}};public List<String> letterCombinations(String digits) {if(digits == null || digits.length() == 0){return result;}backTrace(digits, 0, new char[digits.length()]);return result;}private void backTrace(String digits, int step, char[] path) {if(step == digits.length()) {result.add(new String(path));return;}String opt = phoneMap.get(digits.charAt(step));for(int i = 0; i < opt.length(); i++) {path[step] = opt.charAt(i);backTrace(digits, step + 1, path);}}}
77.组合(中等) 给n个数返回所有k个数的组合
回溯
class Solution {private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> combine(int n, int k) {backTrace(n, 1, k, new ArrayList<Integer>());return res;}private void backTrace(int n, int step, int k, List<Integer> path) {if(path.size() == k) {res.add(new ArrayList<>(path));return;}// 剪枝if(step > n) {return;}// 决策:不选backTrace(n, step + 1, k, path);// 决策:选path.add(step);backTrace(n, step + 1, k, path);path.remove(path.size() - 1);}}
回溯+剪枝优化
解题思路:
关键在于剪枝
class Solution {private List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> combine(int n, int k) {backTrace(n, k, 1, new ArrayList<>());return result;}private void backTrace(int n, int k, int cur, List<Integer> path) {if(path.size() == k) {result.add(new ArrayList<Integer>(path));return;}// n - k + path.size() + 1 => 剪枝的关键// 搜索起点的上界 + 接下来要选择的元素个数 - 1 = n// 搜索起点的上界 = n - (k - path.size()) + 1for(int i = cur; i <= n - k + path.size() + 1; i++) {path.add(i);backTrace(n, k, i + 1, path);path.remove(path.size() - 1);}}}
78.子集(中等) 所有的组合
解题思路:
class Solution {private List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> subsets(int[] nums) {backTrace(nums, 0, new ArrayList<>());return result;}private void backTrace(int[] nums, int stage, List<Integer> path) {if(stage == nums.length) {result.add(new ArrayList<>(path));return;}// 决策:不选backTrace(nums, stage + 1, path);// 决策:选path.add(nums[stage]);backTrace(nums, stage + 1, path);path.remove(path.size() - 1);}}
90.子集II(中等)有重复数据
回溯
剪枝思路:
- 先对数值排序
- 考虑数组 [1,2,2],选择前两个数,或者第一、三个数,都会得到相同的子集。也就是说,对于当前选择的数 x,若前面有与其相同的数 y,且没有选择 y,此时包含 x 的子集,必然会出现在包含 y 的所有子集中。
画决策树也能发现规律。
class Solution {private List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> subsetsWithDup(int[] nums) {// 核心Arrays.sort(nums);backTrace(nums, 0, false, new ArrayList<Integer>());return result;}private void backTrace(int[] nums, int k, boolean choose, List<Integer> path) {if(k == nums.length) {result.add(new ArrayList<>(path));return;}backTrace(nums, k + 1, false, path);// 剪枝,重复的要移除,画决策树,会发现,当做出不选择的决策后的路径,可能与上一阶段的路径相同,这时候得剪枝。if(!choose && k > 0 && nums[k] == nums[k - 1]) {return;}path.add(nums[k]);backTrace(nums, k + 1, true, path);path.remove(path.size() - 1);}}
46.全排列(中等) 所有排列
回溯
class Solution {private List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> permute(int[] nums) {backTrace(nums, 0, new ArrayList<Integer>());return result;}private void backTrace(int[] nums, int k, List<Integer> path) {if(k == nums.length) {result.add(new ArrayList<>(path));return;}for(int i : nums) {if(path.contains(i)) {continue;}// 做选择path.add(i);// 递归backTrace(nums, k + 1, path);// 回溯,撤销选择path.remove(path.size() - 1);}}}
47.全排列II(中等) 有重复数据
class Solution {private List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> combinationSum2(int[] candidates, int target) {// 核心Arrays.sort(candidates);backTrace(candidates, 0, target, new ArrayList<Integer>());return result;}private void backTrace(int[] candidates, int k, int target, List<Integer> path) {if(target == 0) {result.add(new ArrayList<>(path));return;}for(int i = k; i < candidates.length; i++) {// 剪枝if(target - candidates[i] < 0) {return;}// 剪枝if(i > k && candidates[i] == candidates[i - 1]) {continue;}path.add(candidates[i]);backTrace(candidates, i + 1, target - candidates[i], path);path.remove(path.size() - 1);}}}
39.组合总和(中等) 选出某几个数相加为给定和,无重复数据,可以使用多次,不能有重复答案
排序+回溯
解题思路:
- 先将数组排序。
由于数组内元素可以重复使用,那么如果数组[1, 2, 3] 能满足题目,那么无论当前阶段选数组内哪个数,结果集都有可能重复,所以为了结果集不重复,可选列表的范围应为当前所选值到数组尾部。
class Solution {private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> combinationSum(int[] candidates, int target) {// 排序是核心Arrays.sort(candidates);backTrace(candidates, 0, target, new ArrayList<Integer>());return res;}private void backTrace(int[] candidates, int begin, int target, List<Integer> path) {if(target == 0) {res.add(new ArrayList<Integer>(path));}for(int i = begin; i < candidates.length; i++) {int diff = target - candidates[i];// 剪枝,当前差值小于0,后续直接剪枝,无需就相加if(diff < 0){return;}path.add(candidates[i]);backTrace(candidates, i, diff, path);path.remove(path.size() - 1);}}}
40.组合总和II(中等)选出某几个数相加为给定和,有重复数据,只能使用一次,不能有重复答案
- 先将数组排序
- 考虑数组 [1,2,2],选择前两个数,或者第一、三个数,都会得到相同的子集。也就是说,对于当前选择的数 x,若前面有与其相同的数 y,且没有选择 y,此时包含 x 的子集,必然会出现在包含 y 的所有子集中。
由于数组内元素不可重复使用,那么无论当前阶段选数组内哪个数,结果集都有可能重复,所以为了结果集不重复,可选列表的范围应为当前所选值后一位到数组尾部。
class Solution {private List<List<Integer>> result = new ArrayList<>();public List<List<Integer>> combinationSum2(int[] candidates, int target) {Arrays.sort(candidates);backTrace(candidates, 0, target, new ArrayList<Integer>());return result;}private void backTrace(int[] candidates, int k, int target, List<Integer> path) {if(target == 0) {result.add(new ArrayList<>(path));return;}for(int i = k; i < candidates.length; i++) {// 剪枝if(target - candidates[i] < 0) {return;}// 剪枝if(i > k && candidates[i] == candidates[i - 1]) {continue;}path.add(candidates[i]);backTrace(candidates, i + 1, target - candidates[i], path);path.remove(path.size() - 1);}}}
216.组合总和III(中等) 选出k个数相加为给定和,没有重复数据,只能使用一次
class Solution {List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> combinationSum3(int k, int n) {backTrace(k, n, 1, new ArrayList<Integer>());return res;}private void backTrace(int k, int n, int stage, List<Integer> path) {if(n == 0 && path.size() == k) {res.add(new ArrayList<Integer>(path));return;}if(path.size() == k || stage == 10) {return;}// 不选backTrace(k, n, stage + 1, path);// 剪枝if(n - stage < 0) {return;}// 选path.add(stage);backTrace(k, n - stage, stage + 1, path);path.remove(path.size() - 1);}}
131.分割回文串(中等)
class Solution {List<List<String>> res = new ArrayList<>();public List<List<String>> partition(String s) {backTrace(s, s.length(), 0, new ArrayList<String>());return res;}private void backTrace(String s, int len, int index, List<String> path) {if(index == len) {res.add(new ArrayList<>(path));return;}for(int i = index; i < len; i++) {String t = s.substring(index, i + 1);if(!isPalindrome(t)){continue;}path.add(t);backTrace(s, len, i + 1, path);path.remove(path.size() - 1);}}private boolean isPalindrome(String s) {if(s.length() == 0) {return false;}int begin = 0, end = s.length() - 1;while(begin <= end) {if(s.charAt(begin) != s.charAt(end)) {return false;}begin++;end--;}return true;}}
93. 复原IP地址(中等)
class Solution {private List<String> res = new ArrayList<>();public List<String> restoreIpAddresses(String s) {backTrace(s, 0, 4, new ArrayList<String>());return res;}private void backTrace(String s, int index, int step, List<String> path) {if(step == 0 && index == s.length()) {StringBuilder sb = new StringBuilder();for(String num : path) {sb.append(num).append(".");}res.add(sb.toString().substring(0, sb.length() - 1));}// 剪枝int len = s.length() - index;if(len > step * 3 || len < step * 1) {return;}for(int i = index; i < s.length() && i < index + 3; i++) {String num = s.substring(index, i + 1);// 剪枝if(!isVaild(num)) {break;}path.add(num);backTrace(s, i + 1, step - 1, path);path.remove(path.size() - 1);}}private boolean isVaild(String s) {if(s.length() > 1 && s.charAt(0) == '0') {return false;}int num = Integer.parseInt(s);return num >= 0 && num <= 255;}}
22.括号生成(中等)
回溯+栈
class Solution {List<String> res = new ArrayList<>();private Stack<Character> stack = new Stack<>();public List<String> generateParenthesis(int n) {backTrace(n, n * 2, new ArrayList<Character>());return res;}private void backTrace(int n, int step, List<Character> path) {if(step == 0 && stack.isEmpty()) {StringBuilder sb = new StringBuilder();for(Character c : path) {sb.append(c + "");}res.add(sb.toString());return;}if(step < 0) {return;}path.add('(');stack.push('(');backTrace(n, step - 1, path);path.remove(path.size() - 1);stack.pop();// 剪枝if(stack.size() > n) {return;}if(!stack.isEmpty() && stack.peek() == '(') {stack.pop();path.add(')');backTrace(n, step - 1, path);path.remove(path.size() - 1);stack.push('(');}}}
回溯-优化
class Solution {List<String> res = new ArrayList<>();public List<String> generateParenthesis(int n) {backTrace(n, 0, 0, new StringBuilder());return res;}private void backTrace(int max, int left, int right, StringBuilder cur) {if(cur.length() == max * 2) {res.add(cur.toString());return;}if(left < max) {cur.append("(");backTrace(max, left + 1, right, cur);cur.deleteCharAt(cur.length() - 1);}if(right < left) {cur.append(")");backTrace(max, left, right + 1, cur);cur.deleteCharAt(cur.length() - 1);}}}
