回溯

面试题08.12.八皇后(困难)

回溯算法

  1. class Solution {
  2. private List<List<String>> result = new ArrayList<>();
  3. public List<List<String>> solveNQueens(int n) {
  4. char[][] nums = new char[n][n];
  5. // 初始化
  6. for(int i = 0; i < n; i++) {
  7. nums[i] = new char[n];
  8. for(int j = 0; j < n; j++) {
  9. nums[i][j] = '.';
  10. }
  11. }
  12. backTrace(nums, 0);
  13. return result;
  14. }
  15. private void backTrace(char[][] nums, int step) {
  16. if(step == nums.length) {
  17. List<String> snapshot = new ArrayList<>();
  18. for(char[] num : nums) {
  19. snapshot.add(new String(num));
  20. }
  21. result.add(snapshot);
  22. }
  23. for(int i = 0; i < nums.length; i++) {
  24. if(isOk(nums, step, i)) {
  25. nums[step][i] = 'Q';
  26. backTrace(nums, step + 1);
  27. nums[step][i] = '.';
  28. }
  29. }
  30. }
  31. private boolean isOk(char[][] nums, int step, int idx) {
  32. int row = step;
  33. int col = idx;
  34. // 向上检测
  35. while(row > 0) {
  36. if(nums[--row][col] == 'Q') {
  37. return false;
  38. }
  39. }
  40. row = step;
  41. col = idx;
  42. // 向左上角进行检测
  43. while(row > 0 && col > 0) {
  44. if(nums[--row][--col] == 'Q') {
  45. return false;
  46. }
  47. }
  48. row = step;
  49. col = idx;
  50. // 向右上角进行检测
  51. while(row > 0 && col < nums.length - 1) {
  52. if(nums[--row][++col] == 'Q') {
  53. return false;
  54. }
  55. }
  56. return true;
  57. }
  58. }

37. 解数独

回溯

  1. class Solution {
  2. // 记录每行 1~9 之间的某个数字是否出现过
  3. private boolean[][] rows = new boolean[9][9];
  4. // 记录每列 1~9 之间的某个数字是否出现过
  5. private boolean[][] cols = new boolean[9][9];
  6. // 记录每个 3*3 小宫格内 1~9 之间的某个数字是否出现过
  7. private boolean[][][] block = new boolean[3][3][9];
  8. // 记录空白格 “.” 的位置
  9. private List<int[]> space = new ArrayList<>();
  10. private boolean isVaild = false;
  11. public void solveSudoku(char[][] board) {
  12. // 初始化 rows、cols、block 以及 space
  13. for(int i = 0; i < 9; i++) {
  14. for(int j = 0; j < 9; j++) {
  15. if(board[i][j] == '.') {
  16. space.add(new int[]{i, j});
  17. } else {
  18. int idx = board[i][j] - '0' - 1;
  19. rows[i][idx] = cols[j][idx] = block[i / 3][j / 3][idx] = true;
  20. }
  21. }
  22. }
  23. backTrace(board, 0);
  24. }
  25. private void backTrace(char[][] board, int step) {
  26. if(step == space.size()) {
  27. isVaild = true;
  28. return;
  29. }
  30. int[] pos = space.get(step);
  31. int row = pos[0], col = pos[1];
  32. // !isVaild 剪枝
  33. for(int i = 0; i < 9 && !isVaild; i++) {
  34. if(!rows[row][i] && !cols[col][i] && !block[row / 3][col / 3][i]) {
  35. rows[row][i] = cols[col][i] = block[row / 3][col / 3][i] = true;
  36. board[row][col] = (char)(i + '0' + 1);
  37. backTrace(board, step + 1);
  38. rows[row][i] = cols[col][i] = block[row / 3][col / 3][i] = false;
  39. }
  40. }
  41. }
  42. }

17.电话号码的字母组合(中等)

  1. class Solution {
  2. private List<String> result = new ArrayList<>();
  3. Map<Character, String> phoneMap = new HashMap<Character, String>() {{
  4. put('2', "abc");
  5. put('3', "def");
  6. put('4', "ghi");
  7. put('5', "jkl");
  8. put('6', "mno");
  9. put('7', "pqrs");
  10. put('8', "tuv");
  11. put('9', "wxyz");
  12. }};
  13. public List<String> letterCombinations(String digits) {
  14. if(digits == null || digits.length() == 0){
  15. return result;
  16. }
  17. backTrace(digits, 0, new char[digits.length()]);
  18. return result;
  19. }
  20. private void backTrace(String digits, int step, char[] path) {
  21. if(step == digits.length()) {
  22. result.add(new String(path));
  23. return;
  24. }
  25. String opt = phoneMap.get(digits.charAt(step));
  26. for(int i = 0; i < opt.length(); i++) {
  27. path[step] = opt.charAt(i);
  28. backTrace(digits, step + 1, path);
  29. }
  30. }
  31. }

77.组合(中等) 给n个数返回所有k个数的组合

回溯

  1. class Solution {
  2. private List<List<Integer>> res = new ArrayList<>();
  3. public List<List<Integer>> combine(int n, int k) {
  4. backTrace(n, 1, k, new ArrayList<Integer>());
  5. return res;
  6. }
  7. private void backTrace(int n, int step, int k, List<Integer> path) {
  8. if(path.size() == k) {
  9. res.add(new ArrayList<>(path));
  10. return;
  11. }
  12. // 剪枝
  13. if(step > n) {
  14. return;
  15. }
  16. // 决策:不选
  17. backTrace(n, step + 1, k, path);
  18. // 决策:选
  19. path.add(step);
  20. backTrace(n, step + 1, k, path);
  21. path.remove(path.size() - 1);
  22. }
  23. }

回溯+剪枝优化

解题思路:
关键在于剪枝

  1. class Solution {
  2. private List<List<Integer>> result = new ArrayList<>();
  3. public List<List<Integer>> combine(int n, int k) {
  4. backTrace(n, k, 1, new ArrayList<>());
  5. return result;
  6. }
  7. private void backTrace(int n, int k, int cur, List<Integer> path) {
  8. if(path.size() == k) {
  9. result.add(new ArrayList<Integer>(path));
  10. return;
  11. }
  12. // n - k + path.size() + 1 => 剪枝的关键
  13. // 搜索起点的上界 + 接下来要选择的元素个数 - 1 = n
  14. // 搜索起点的上界 = n - (k - path.size()) + 1
  15. for(int i = cur; i <= n - k + path.size() + 1; i++) {
  16. path.add(i);
  17. backTrace(n, k, i + 1, path);
  18. path.remove(path.size() - 1);
  19. }
  20. }
  21. }

78.子集(中等) 所有的组合

解题思路:

  1. class Solution {
  2. private List<List<Integer>> result = new ArrayList<>();
  3. public List<List<Integer>> subsets(int[] nums) {
  4. backTrace(nums, 0, new ArrayList<>());
  5. return result;
  6. }
  7. private void backTrace(int[] nums, int stage, List<Integer> path) {
  8. if(stage == nums.length) {
  9. result.add(new ArrayList<>(path));
  10. return;
  11. }
  12. // 决策:不选
  13. backTrace(nums, stage + 1, path);
  14. // 决策:选
  15. path.add(nums[stage]);
  16. backTrace(nums, stage + 1, path);
  17. path.remove(path.size() - 1);
  18. }
  19. }

90.子集II(中等)有重复数据

回溯

剪枝思路:

  1. 先对数值排序
  2. 考虑数组 [1,2,2],选择前两个数,或者第一、三个数,都会得到相同的子集。也就是说,对于当前选择的数 x,若前面有与其相同的数 y,且没有选择 y,此时包含 x 的子集,必然会出现在包含 y 的所有子集中。
  3. 画决策树也能发现规律。

    1. class Solution {
    2. private List<List<Integer>> result = new ArrayList<>();
    3. public List<List<Integer>> subsetsWithDup(int[] nums) {
    4. // 核心
    5. Arrays.sort(nums);
    6. backTrace(nums, 0, false, new ArrayList<Integer>());
    7. return result;
    8. }
    9. private void backTrace(int[] nums, int k, boolean choose, List<Integer> path) {
    10. if(k == nums.length) {
    11. result.add(new ArrayList<>(path));
    12. return;
    13. }
    14. backTrace(nums, k + 1, false, path);
    15. // 剪枝,重复的要移除,画决策树,会发现,当做出不选择的决策后的路径,可能与上一阶段的路径相同,这时候得剪枝。
    16. if(!choose && k > 0 && nums[k] == nums[k - 1]) {
    17. return;
    18. }
    19. path.add(nums[k]);
    20. backTrace(nums, k + 1, true, path);
    21. path.remove(path.size() - 1);
    22. }
    23. }

46.全排列(中等) 所有排列

回溯

  1. class Solution {
  2. private List<List<Integer>> result = new ArrayList<>();
  3. public List<List<Integer>> permute(int[] nums) {
  4. backTrace(nums, 0, new ArrayList<Integer>());
  5. return result;
  6. }
  7. private void backTrace(int[] nums, int k, List<Integer> path) {
  8. if(k == nums.length) {
  9. result.add(new ArrayList<>(path));
  10. return;
  11. }
  12. for(int i : nums) {
  13. if(path.contains(i)) {
  14. continue;
  15. }
  16. // 做选择
  17. path.add(i);
  18. // 递归
  19. backTrace(nums, k + 1, path);
  20. // 回溯,撤销选择
  21. path.remove(path.size() - 1);
  22. }
  23. }
  24. }

47.全排列II(中等) 有重复数据

  1. class Solution {
  2. private List<List<Integer>> result = new ArrayList<>();
  3. public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  4. // 核心
  5. Arrays.sort(candidates);
  6. backTrace(candidates, 0, target, new ArrayList<Integer>());
  7. return result;
  8. }
  9. private void backTrace(int[] candidates, int k, int target, List<Integer> path) {
  10. if(target == 0) {
  11. result.add(new ArrayList<>(path));
  12. return;
  13. }
  14. for(int i = k; i < candidates.length; i++) {
  15. // 剪枝
  16. if(target - candidates[i] < 0) {
  17. return;
  18. }
  19. // 剪枝
  20. if(i > k && candidates[i] == candidates[i - 1]) {
  21. continue;
  22. }
  23. path.add(candidates[i]);
  24. backTrace(candidates, i + 1, target - candidates[i], path);
  25. path.remove(path.size() - 1);
  26. }
  27. }
  28. }

39.组合总和(中等) 选出某几个数相加为给定和,无重复数据,可以使用多次,不能有重复答案

排序+回溯

解题思路:

  1. 先将数组排序。
  2. 由于数组内元素可以重复使用,那么如果数组[1, 2, 3] 能满足题目,那么无论当前阶段选数组内哪个数,结果集都有可能重复,所以为了结果集不重复,可选列表的范围应为当前所选值到数组尾部。

    1. class Solution {
    2. private List<List<Integer>> res = new ArrayList<>();
    3. public List<List<Integer>> combinationSum(int[] candidates, int target) {
    4. // 排序是核心
    5. Arrays.sort(candidates);
    6. backTrace(candidates, 0, target, new ArrayList<Integer>());
    7. return res;
    8. }
    9. private void backTrace(int[] candidates, int begin, int target, List<Integer> path) {
    10. if(target == 0) {
    11. res.add(new ArrayList<Integer>(path));
    12. }
    13. for(int i = begin; i < candidates.length; i++) {
    14. int diff = target - candidates[i];
    15. // 剪枝,当前差值小于0,后续直接剪枝,无需就相加
    16. if(diff < 0){
    17. return;
    18. }
    19. path.add(candidates[i]);
    20. backTrace(candidates, i, diff, path);
    21. path.remove(path.size() - 1);
    22. }
    23. }
    24. }

40.组合总和II(中等)选出某几个数相加为给定和,有重复数据,只能使用一次,不能有重复答案

  1. 先将数组排序
  2. 考虑数组 [1,2,2],选择前两个数,或者第一、三个数,都会得到相同的子集。也就是说,对于当前选择的数 x,若前面有与其相同的数 y,且没有选择 y,此时包含 x 的子集,必然会出现在包含 y 的所有子集中。
  3. 由于数组内元素不可重复使用,那么无论当前阶段选数组内哪个数,结果集都有可能重复,所以为了结果集不重复,可选列表的范围应为当前所选值后一位到数组尾部。

    1. class Solution {
    2. private List<List<Integer>> result = new ArrayList<>();
    3. public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    4. Arrays.sort(candidates);
    5. backTrace(candidates, 0, target, new ArrayList<Integer>());
    6. return result;
    7. }
    8. private void backTrace(int[] candidates, int k, int target, List<Integer> path) {
    9. if(target == 0) {
    10. result.add(new ArrayList<>(path));
    11. return;
    12. }
    13. for(int i = k; i < candidates.length; i++) {
    14. // 剪枝
    15. if(target - candidates[i] < 0) {
    16. return;
    17. }
    18. // 剪枝
    19. if(i > k && candidates[i] == candidates[i - 1]) {
    20. continue;
    21. }
    22. path.add(candidates[i]);
    23. backTrace(candidates, i + 1, target - candidates[i], path);
    24. path.remove(path.size() - 1);
    25. }
    26. }
    27. }

216.组合总和III(中等) 选出k个数相加为给定和,没有重复数据,只能使用一次

  1. class Solution {
  2. List<List<Integer>> res = new ArrayList<>();
  3. public List<List<Integer>> combinationSum3(int k, int n) {
  4. backTrace(k, n, 1, new ArrayList<Integer>());
  5. return res;
  6. }
  7. private void backTrace(int k, int n, int stage, List<Integer> path) {
  8. if(n == 0 && path.size() == k) {
  9. res.add(new ArrayList<Integer>(path));
  10. return;
  11. }
  12. if(path.size() == k || stage == 10) {
  13. return;
  14. }
  15. // 不选
  16. backTrace(k, n, stage + 1, path);
  17. // 剪枝
  18. if(n - stage < 0) {
  19. return;
  20. }
  21. // 选
  22. path.add(stage);
  23. backTrace(k, n - stage, stage + 1, path);
  24. path.remove(path.size() - 1);
  25. }
  26. }

131.分割回文串(中等)

  1. class Solution {
  2. List<List<String>> res = new ArrayList<>();
  3. public List<List<String>> partition(String s) {
  4. backTrace(s, s.length(), 0, new ArrayList<String>());
  5. return res;
  6. }
  7. private void backTrace(String s, int len, int index, List<String> path) {
  8. if(index == len) {
  9. res.add(new ArrayList<>(path));
  10. return;
  11. }
  12. for(int i = index; i < len; i++) {
  13. String t = s.substring(index, i + 1);
  14. if(!isPalindrome(t)){
  15. continue;
  16. }
  17. path.add(t);
  18. backTrace(s, len, i + 1, path);
  19. path.remove(path.size() - 1);
  20. }
  21. }
  22. private boolean isPalindrome(String s) {
  23. if(s.length() == 0) {
  24. return false;
  25. }
  26. int begin = 0, end = s.length() - 1;
  27. while(begin <= end) {
  28. if(s.charAt(begin) != s.charAt(end)) {
  29. return false;
  30. }
  31. begin++;
  32. end--;
  33. }
  34. return true;
  35. }
  36. }

93. 复原IP地址(中等)

  1. class Solution {
  2. private List<String> res = new ArrayList<>();
  3. public List<String> restoreIpAddresses(String s) {
  4. backTrace(s, 0, 4, new ArrayList<String>());
  5. return res;
  6. }
  7. private void backTrace(String s, int index, int step, List<String> path) {
  8. if(step == 0 && index == s.length()) {
  9. StringBuilder sb = new StringBuilder();
  10. for(String num : path) {
  11. sb.append(num).append(".");
  12. }
  13. res.add(sb.toString().substring(0, sb.length() - 1));
  14. }
  15. // 剪枝
  16. int len = s.length() - index;
  17. if(len > step * 3 || len < step * 1) {
  18. return;
  19. }
  20. for(int i = index; i < s.length() && i < index + 3; i++) {
  21. String num = s.substring(index, i + 1);
  22. // 剪枝
  23. if(!isVaild(num)) {
  24. break;
  25. }
  26. path.add(num);
  27. backTrace(s, i + 1, step - 1, path);
  28. path.remove(path.size() - 1);
  29. }
  30. }
  31. private boolean isVaild(String s) {
  32. if(s.length() > 1 && s.charAt(0) == '0') {
  33. return false;
  34. }
  35. int num = Integer.parseInt(s);
  36. return num >= 0 && num <= 255;
  37. }
  38. }

22.括号生成(中等)

回溯+栈

  1. class Solution {
  2. List<String> res = new ArrayList<>();
  3. private Stack<Character> stack = new Stack<>();
  4. public List<String> generateParenthesis(int n) {
  5. backTrace(n, n * 2, new ArrayList<Character>());
  6. return res;
  7. }
  8. private void backTrace(int n, int step, List<Character> path) {
  9. if(step == 0 && stack.isEmpty()) {
  10. StringBuilder sb = new StringBuilder();
  11. for(Character c : path) {
  12. sb.append(c + "");
  13. }
  14. res.add(sb.toString());
  15. return;
  16. }
  17. if(step < 0) {
  18. return;
  19. }
  20. path.add('(');
  21. stack.push('(');
  22. backTrace(n, step - 1, path);
  23. path.remove(path.size() - 1);
  24. stack.pop();
  25. // 剪枝
  26. if(stack.size() > n) {
  27. return;
  28. }
  29. if(!stack.isEmpty() && stack.peek() == '(') {
  30. stack.pop();
  31. path.add(')');
  32. backTrace(n, step - 1, path);
  33. path.remove(path.size() - 1);
  34. stack.push('(');
  35. }
  36. }
  37. }

回溯-优化

  1. class Solution {
  2. List<String> res = new ArrayList<>();
  3. public List<String> generateParenthesis(int n) {
  4. backTrace(n, 0, 0, new StringBuilder());
  5. return res;
  6. }
  7. private void backTrace(int max, int left, int right, StringBuilder cur) {
  8. if(cur.length() == max * 2) {
  9. res.add(cur.toString());
  10. return;
  11. }
  12. if(left < max) {
  13. cur.append("(");
  14. backTrace(max, left + 1, right, cur);
  15. cur.deleteCharAt(cur.length() - 1);
  16. }
  17. if(right < left) {
  18. cur.append(")");
  19. backTrace(max, left, right + 1, cur);
  20. cur.deleteCharAt(cur.length() - 1);
  21. }
  22. }
  23. }