LeetCode——Backtracking

末蓝、 2023-06-19 09:38 117阅读 0赞

Backtracking


目录

  1. Backtracking
  2. 数字键盘组合
  3. IP 地址划分
  4. 在矩阵中寻找字符串
  5. 输出二叉树中所有从根到叶子的路径
  6. 排列
  7. 含有相同元素求排列
  8. 组合
  9. 组合求和
  10. 含有相同元素的组合求和
  11. 1-9 数字的组合求和
  12. 子集
  13. 含有相同元素求子集
  14. 分割字符串使得每个部分都是回文数
  15. 数独
  16. N皇后

1. Backtracking

Backtracking(回溯)属于 DFS。
普通 DFS 主要用在可达性问题,这种问题只需要执行到特点的位置然后返回即可。

而 Backtracking 主要用于求解 排列组合 问题,例如有 { ‘a’,‘b’,‘c’ } 三个字符,求解所有由这三个字符排列得到的字符串,这种问题在执行到特定的位置返回之后还会继续执行求解过程。

因为 Backtracking 不是立即返回,而要继续求解,因此在程序实现时,需要注意对元素的标记问题:
在访问一个新元素进入新的递归调用时,需要将新元素标记为已经访问,这样才能在继续递归调用时不用重复访问该元素;

但是在递归返回时,需要将元素标记为未访问,因为只需要保证在一个递归链中不同时访问一个元素,可以访问已经访问过但是不在当前递归链中的元素。


2. 数字键盘组合

https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number
在这里插入图片描述

  1. private static final String[] KEYS = {
  2. "","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
  3. public List<String> letterCombinations(String digits){
  4. List<String> combinations = new ArrayList<>();
  5. if (digits==null||digits.length()==0){
  6. return combinations;
  7. }
  8. doCombination(new StringBuilder(),combinations,digits);
  9. return combinations;
  10. }
  11. private void doCombination(StringBuilder prefix, List<String> combinations, final String digits) {
  12. if (prefix.length()==digits.length()){
  13. combinations.add(prefix.toString());
  14. return;
  15. }
  16. int num = digits.charAt(prefix.length()) - '0';
  17. String key = KEYS[num];
  18. for (char c : key.toCharArray()) {
  19. prefix.append(c);
  20. doCombination(prefix,combinations,digits);
  21. prefix.deleteCharAt(prefix.length()-1);
  22. }
  23. }

3. IP 地址划分

https://leetcode-cn.com/problems/restore-ip-addresses

在这里插入图片描述

  1. public List<String> restoreIpAddresses(String s) {
  2. List<String> addresses = new ArrayList<>();
  3. StringBuilder tempAddress = new StringBuilder();
  4. doRestore(0, tempAddress, addresses, s);
  5. return addresses;
  6. }
  7. private void doRestore(int k, StringBuilder tempAddress, List<String> addresses, String s) {
  8. //整体分成4个段
  9. if (k == 4 || s.length() == 0) {
  10. if (k == 4 && s.length() == 0) {
  11. addresses.add(tempAddress.toString());
  12. }
  13. return;
  14. }
  15. //每个段的判断
  16. for (int i = 0; i < s.length() && i <= 2; i++) {
  17. if (i != 0 && s.charAt(0) == '0') {
  18. break;
  19. }
  20. String part = s.substring(0, i + 1);
  21. if (Integer.valueOf(part) <= 255) {
  22. if (tempAddress.length() != 0) {
  23. part = "." + part;
  24. }
  25. tempAddress.append(part);
  26. doRestore(k + 1, tempAddress, addresses, s.substring(i + 1));
  27. tempAddress.delete(tempAddress.length() - part.length(), tempAddress.length());
  28. }
  29. }
  30. }

4. 在矩阵中寻找字符串

https://leetcode-cn.com/problems/word-search

在这里插入图片描述
题目描述:给定一个二维网格和一个单词,找出该单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

  1. private final static int[][] direction = {
  2. {
  3. 1, 0}, {
  4. -1, 0}, {
  5. 0, 1}, {
  6. 0, -1}};
  7. private int m, n;
  8. public boolean exist(char[][] board, String word) {
  9. if (word == null || word.length() == 0) {
  10. return true;
  11. }
  12. if (board == null || board.length == 0 || board[0].length == 0) {
  13. return false;
  14. }
  15. m = board.length;
  16. n = board[0].length;
  17. boolean[][] visited = new boolean[m][n];
  18. for (int r = 0; r < m; r++) {
  19. for (int c = 0; c < n; c++) {
  20. if (backtracking(0, r, c, visited, board, word)) {
  21. return true;
  22. }
  23. }
  24. }
  25. return false;
  26. }
  27. private boolean backtracking(int curLen, int r, int c, boolean[][] visited, final char[][] board, final String word) {
  28. if (curLen == word.length()) {
  29. return true;
  30. }
  31. if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(curLen) || visited[r][c]) {
  32. return false;
  33. }
  34. visited[r][c] = true;
  35. for (int[] d : direction) {
  36. if (backtracking(curLen + 1, r + d[0], c + d[1], visited, board, word)) {
  37. return true;
  38. }
  39. }
  40. visited[r][c] = false;
  41. return false;
  42. }

5. 输出二叉树中所有从根到叶子的路径

https://leetcode.com/problems/binary-tree-paths
在这里插入图片描述

  1. public static class TreeNode {
  2. int val = 0;
  3. TreeNode left = null;
  4. TreeNode right = null;
  5. public TreeNode(int val) {
  6. this.val = val;
  7. }
  8. }
  9. public List<String> binaryTreePaths(TreeNode root) {
  10. List<String> paths = new ArrayList<>();
  11. if (root == null) {
  12. return paths;
  13. }
  14. List<Integer> values = new ArrayList<>();
  15. backtracking(root, values, paths);
  16. return paths;
  17. }
  18. private void backtracking(TreeNode node, List<Integer> values, List<String> paths) {
  19. if (node == null) {
  20. return;
  21. }
  22. values.add(node.val);
  23. if (isLeaf(node)) {
  24. paths.add(buildPath(values));
  25. } else {
  26. backtracking(node.left, values, paths);
  27. backtracking(node.right, values, paths);
  28. }
  29. values.remove(values.size() - 1);
  30. }
  31. private String buildPath(List<Integer> values) {
  32. StringBuilder str = new StringBuilder();
  33. for (int i = 0; i < values.size(); i++) {
  34. str.append(values.get(i));
  35. if (i != values.size() - 1) {
  36. str.append("->");
  37. }
  38. }
  39. return str.toString();
  40. }
  41. private boolean isLeaf(TreeNode node) {
  42. return node.left == null && node.right == null;
  43. }

6. 排列

在这里插入图片描述
题目描述:给定一个没有重复数字的序列,返回其所有可能的全排列。

  1. public List<List<Integer>> permute(int[] nums) {
  2. List<List<Integer>> permutes = new ArrayList<>();
  3. List<Integer> permuteList = new ArrayList<>();
  4. boolean[] hasVisited = new boolean[nums.length];
  5. backtracking(permuteList, permutes, hasVisited, nums);
  6. return permutes;
  7. }
  8. private void backtracking(List<Integer> permuteList, List<List<Integer>> permutes, boolean[] visited, int[] nums) {
  9. if (permuteList.size() == nums.length) {
  10. permutes.add(new ArrayList<>(permuteList)); //重新构造一个 List
  11. return;
  12. }
  13. for (int i = 0; i < visited.length; i++) {
  14. if (visited[i]) {
  15. continue;
  16. }
  17. visited[i] = true;
  18. permuteList.add(nums[i]);
  19. backtracking(permuteList, permutes, visited, nums);
  20. permuteList.remove(permuteList.size() - 1);
  21. visited[i] = false;
  22. }
  23. }

7. 含有相同元素求排列

在这里插入图片描述
题目描述:数组元素可能含有相同的元素,进行排列时就有可能出现重复的排列,要求重复的排列只返回一个。

在实现上,和 Permutations 不同的是要先排序,然后在添加一个元素时,判断这个元素是否等于前一个元素,如果等于,并且前一个元素还未访问,那么就跳过这个元素。

  1. public List<List<Integer>> permuteUnique(int[] nums) {
  2. List<List<Integer>> permutes = new ArrayList<>();
  3. List<Integer> permuteList = new ArrayList<>();
  4. Arrays.sort(nums);
  5. boolean[] hasVisited = new boolean[nums.length];
  6. backtracking(permuteList, permutes, hasVisited, nums);
  7. return permutes;
  8. }
  9. private void backtracking(List<Integer> permuteList, List<List<Integer>> permutes, boolean[] visited, int[] nums) {
  10. if (permuteList.size() == nums.length) {
  11. permutes.add(new ArrayList<>(permuteList));
  12. return;
  13. }
  14. for (int i = 0; i < visited.length; i++) {
  15. if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
  16. continue; //防止重复
  17. }
  18. if (visited[i]) {
  19. continue;
  20. }
  21. visited[i] = true;
  22. permuteList.add(nums[i]);
  23. backtracking(permuteList, permutes, visited, nums);
  24. permuteList.remove(permuteList.size() - 1);
  25. visited[i] = false;
  26. }
  27. }

8. 组合

https://leetcode-cn.com/problems/combinations
在这里插入图片描述

  1. public List<List<Integer>> combine(int n, int k) {
  2. List<List<Integer>> combinations = new ArrayList<>();
  3. List<Integer> combineList = new ArrayList<>();
  4. backtracking(combineList, combinations, 1, k, n);
  5. return combinations;
  6. }
  7. private void backtracking(List<Integer> combineList, List<List<Integer>> combinations, int start, int k, int n) {
  8. if (k == 0) {
  9. combinations.add(new ArrayList<>(combineList));
  10. return;
  11. }
  12. for (int i = start; i <= n - k + 1; i++) {
  13. combineList.add(i);
  14. backtracking(combineList, combinations, i + 1, k - 1, n);
  15. combineList.remove(combineList.size() - 1);
  16. }
  17. }

9. 组合求和

https://leetcode.com/problems/combination-sum/description/
在这里插入图片描述
题目描述:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

    public List> combiationSum(int[] candidates, int target) {

    1. List<List<Integer>> combinations = new ArrayList<>();
    2. backtracking(new ArrayList<>(), combinations, 0, target, candidates);
    3. return combinations;
    4. }
    5. private void backtracking(ArrayList<Integer> tempCombination, List<List<Integer>> combinations, int start, int target, int[] candidates) {
    6. if (target == 0) {
    7. combinations.add(new ArrayList<>(tempCombination));
    8. return;
    9. }
    10. for (int i = start; i < candidates.length; i++) {
    11. if (candidates[i] <= target) {
    12. tempCombination.add(candidates[i]);
    13. backtracking(tempCombination, combinations, i, target - candidates[i], candidates);
    14. tempCombination.remove(tempCombination.size() - 1);
    15. }
    16. }
    17. }

10. 含有相同元素的组合求和

https://leetcode.com/problems/combination-sum-ii/description/
在这里插入图片描述
题目描述:给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

  • 所有数字(包括目标数)都是正整数。
  • 解集不能包含重复的组合。

    public List> combinationSum2(int[] candidates, int target) {

    1. List<List<Integer>> combinations = new ArrayList<>();
    2. Arrays.sort(candidates);
    3. backtracking(combinations, new ArrayList<>(), new boolean[candidates.length], 0, target, candidates);
    4. return combinations;
    5. }
    6. private void backtracking(List<List<Integer>> combinations, List<Integer> combinationList, boolean[] visited, int start, int target, int[] candidates) {
    7. if (target == 0) {
    8. combinations.add(new ArrayList<>(combinationList));
    9. return;
    10. }
    11. for (int i = start; i < candidates.length; i++) {
    12. if (i != 0 && candidates[i] == candidates[i - 1] && !visited[i - 1]) {
    13. continue;
    14. }
    15. if (candidates[i] <= target) {
    16. combinationList.add(candidates[i]);
    17. visited[i] = true;
    18. backtracking(combinations, combinationList, visited, i + 1, target - candidates[i], candidates);
    19. visited[i] = false;
    20. combinationList.remove(combinationList.size() - 1);
    21. }
    22. }
    23. }

11. 1-9 数字的组合求和

https://leetcode.com/problems/combination-sum-iii/description/
在这里插入图片描述
题目描述:找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

  • 所有数字都是正整数。
  • 解集不能包含重复的组合。

    public List> combinationSum3(int k, int n) {

    1. List<List<Integer>> combinations = new ArrayList<>();
    2. List<Integer> combinationList = new ArrayList<>();
    3. backtracking(combinationList, combinations, 1, k, n);
    4. return combinations;
    5. }
    6. private void backtracking(List<Integer> combinationList, List<List<Integer>> combinations, int start, int k, int target) {
    7. if (k == 0 && target == 0) {
    8. combinations.add(new ArrayList<>(combinationList));
    9. return;
    10. }
    11. if (k == 0 || target == 0) {
    12. return;
    13. }
    14. for (int i = start; i <= 9; i++) {
    15. combinationList.add(i);
    16. backtracking(combinationList, combinations, i + 1, k - 1, target - i);
    17. combinationList.remove(combinationList.size() - 1);
    18. }
    19. }

12. 子集

https://leetcode.com/problems/subsets-ii/description/
在这里插入图片描述
题目描述:找出集合的所有子集,子集不能重复,[1, 2] 和 [2, 1] 这种子集算重复

  1. public List<List<Integer>> subsets(int[] nums) {
  2. List<List<Integer>> subsets = new ArrayList<>();
  3. List<Integer> tempSubset = new ArrayList<>();
  4. for (int size = 0; size <= nums.length; size++) {
  5. backtracking(subsets, tempSubset, size, 0, nums);
  6. }
  7. return subsets;
  8. }
  9. private void backtracking(List<List<Integer>> subsets, List<Integer> tempSubset, int size, int start, int[] nums) {
  10. if (size == 0) {
  11. subsets.add(new ArrayList<>(tempSubset));
  12. }
  13. for (int i = start; i < nums.length; i++) {
  14. tempSubset.add(nums[i]);
  15. backtracking(subsets, tempSubset, size - 1, i + 1, nums);
  16. tempSubset.remove(tempSubset.size() - 1);
  17. }
  18. }

13. 含有相同元素求子集

在这里插入图片描述
题目描述:给定一个可能包含重复数的整数集合,返回所有可能的子集(幂集)。

注意:解决方案集不能包含重复的子集。

  1. public List<List<Integer>> subsets(int[] nums) {
  2. List<List<Integer>> subsets = new ArrayList<>();
  3. List<Integer> tempSubset = new ArrayList<>();
  4. for (int size = 0; size <= nums.length; size++) {
  5. backtracking(subsets, tempSubset, size, 0, nums);
  6. }
  7. return subsets;
  8. }
  9. private void backtracking(List<List<Integer>> subsets, List<Integer> tempSubset, int size, int start, int[] nums) {
  10. if (size == 0) {
  11. subsets.add(new ArrayList<>(tempSubset));
  12. }
  13. for (int i = start; i < nums.length; i++) {
  14. tempSubset.add(nums[i]);
  15. backtracking(subsets, tempSubset, size - 1, i + 1, nums);
  16. tempSubset.remove(tempSubset.size() - 1);
  17. }
  18. }

14. 分割字符串使得每个部分都是回文数

https://leetcode-cn.com/problems/palindrome-partitioning
在这里插入图片描述
题目描述:给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

  1. public List<List<String>> partition(String s) {
  2. List<List<String>> partitions = new ArrayList<>();
  3. List<String> tempPartition = new ArrayList<>();
  4. doPartition(s, partitions, tempPartition);
  5. return partitions;
  6. }
  7. private void doPartition(String s, List<List<String>> partitions, List<String> tempPartition) {
  8. if (s.length() == 0) {
  9. partitions.add(new ArrayList<>(tempPartition));
  10. return;
  11. }
  12. for (int i = 0; i < s.length(); i++) {
  13. if (isPalindrome(s, 0, i)) {
  14. tempPartition.add(s.substring(0, i + 1));
  15. doPartition(s.substring(i + 1), partitions, tempPartition);
  16. tempPartition.remove(tempPartition.size() - 1);
  17. }
  18. }
  19. }
  20. private boolean isPalindrome(String s, int begin, int end) {
  21. while (begin < end) {
  22. if (s.charAt(begin++) != s.charAt(end--)) {
  23. return false;
  24. }
  25. }
  26. return true;
  27. }

15. 数独

https://leetcode-cn.com/problems/sudoku-solver/solution/jie-shu-du-by-leetcode/
在这里插入图片描述
在这里插入图片描述
题目描述:编写一个程序,通过已填充的空格来解决数独问题。

一个数独的解法需遵循如下规则:

  • 数字 1-9 在每一行只能出现一次。
  • 数字 1-9 在每一列只能出现一次。
  • 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
  • 空白格用 ‘.’ 表示。

    private boolean[][] rowsUsed = new boolean[9][10];

    1. private boolean[][] colsUsed = new boolean[9][10];
    2. private boolean[][] cubesUsed = new boolean[9][10];
    3. private char[][] board;
    4. public void solveSudoku(char[][] board) {
    5. this.board = board;
    6. for (int i = 0; i < 9; i++) {
    7. for (int j = 0; j < 9; j++) {
    8. if (board[i][j] == '.') {
    9. continue;
    10. }
    11. int num = board[i][j] - '0';
    12. rowsUsed[i][num] = true;
    13. colsUsed[j][num] = true;
    14. cubesUsed[cubes(i, j)][num] = true;
    15. }
    16. backtracking(0, 0);
    17. }
    18. }
    19. private boolean backtracking(int row, int col) {
    20. while (row < 9 && board[row][col] != '.') {
    21. row = col == 8 ? row + 1 : row;
    22. col = col == 8 ? 0 : col + 1;
    23. }
    24. if (row == 9) {
    25. return true;
    26. }
    27. for (int num = 1; num <= 9; num++) {
    28. if (rowsUsed[row][num] || colsUsed[col][num] || cubesUsed[cubes(row, col)][num]) {
    29. continue;
    30. }
    31. rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubes(row, col)][num] == true;
    32. board[row][col] = (char) (num + '0');
    33. if (backtracking(row, col)) {
    34. return true;
    35. }
    36. board[row][col] = '.';
    37. rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubes(row, col)][num] == false;
    38. }
    39. return false;
    40. }
    41. private int cubes(int i, int j) {
    42. int r = i / 3;
    43. int c = j / 3;
    44. return r * 3 + c;
    45. }

16. N皇后

https://leetcode-cn.com/problems/n-queens/solution/nhuang-hou-by-leetcode/在这里插入图片描述
题目描述:n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

在这里插入图片描述

上图为 8 皇后问题的一种解法。

给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

  1. private List<List<String>> solutions;
  2. private char[][] nQueues;
  3. private boolean[] colUsed;
  4. private boolean[] diagonals45Used;
  5. private boolean[] diagonals135Used;
  6. private int n;
  7. public List<List<String>> solveNQueues(int n) {
  8. solutions = new ArrayList<>();
  9. nQueues = new char[n][n];
  10. for (int i = 0; i < n; i++) {
  11. Arrays.fill(nQueues[i], '.');
  12. }
  13. colUsed = new boolean[n];
  14. diagonals45Used = new boolean[2 * n - 1];
  15. diagonals135Used = new boolean[2 * n - 1];
  16. this.n = n;
  17. backtracking(0);
  18. return solutions;
  19. }
  20. private void backtracking(int row) {
  21. if (row == n) {
  22. List<String> list = new ArrayList<>();
  23. for (char[] chars : nQueues) {
  24. list.add(new String(chars));
  25. }
  26. solutions.add(list);
  27. return;
  28. }
  29. for (int col = 0; col < n; col++) {
  30. int diagonals45Idx = row + col;
  31. int diagonals135Idx = n - 1 - (row - col);
  32. if (colUsed[col] || diagonals45Used[diagonals45Idx] || diagonals135Used[diagonals135Idx]) {
  33. continue;
  34. }
  35. nQueues[row][col] = 'Q';
  36. colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = true;
  37. backtracking(row + 1);
  38. colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = false;
  39. nQueues[row][col] = '.';
  40. }
  41. }

发表评论

表情:
评论列表 (有 0 条评论,117人围观)

还没有评论,来说两句吧...

相关阅读