Backtracking of "Algorithm Series"

Introduction

  The backtracking algorithm is a depth-first search algorithm , so the backtracking algorithm has the characteristics of deep search. For example: 1. It is a recursive algorithm . Second, it is a violent algorithm . 3. The essence is to enumerate all possibilities, and then find out the answer we want . In fact, if you encounter a backtracking algorithm question during the interview, you should wake up with a smile. Because this is a kind of question with a "template", which is one-size-fits-all . Instead of questions like dynamic planning, there is no fixed routine, and it depends on fate whether you can do it or not. Retrospective questions are generally of medium difficulty, which is very suitable for interviews and exams . You can thoroughly understand them in a day or two. It can be said that the cost-effectiveness is very high. All we have to do is to memorize the template! Sometimes a question can be solved by the dynamic method, or by the backtracking method. At this time, we should consider using the dynamic method first, because the efficiency of backtracking is still too low .

theoretical basis

  The backtracking algorithm is to perform a depth-first traversal on the tree or graph structure. In fact, it is similar to the enumeration search attempt process, and finds the solution to the problem during the traversal process. Depth-first traversal has a feature: when it is found that the solution condition is not met, it returns and tries another path. At this time, the object type variable needs to be reset to be the same as before, which is called state reset . Many complex and large-scale problems can use the backtracking method, which is known as a general problem-solving method . In fact, the backtracking algorithm is a violent search algorithm . It is an algorithm used in early artificial intelligence. It helps us find solutions to problems with the help of powerful computing power of computers. In general, backtracking can be used to solve combination problems , cutting problems , subset problems , permutation problems , and chessboard problems . The characteristics of these problems are: they can all be abstracted into a tree structure.

backtracking template

  Next is the most important part of the template. The backtracking template is actually evolved from the recursion of Deep Search. Note the following points:

  • In the backtracking algorithm, the return value of the function is generally void, but generally a result set is required to store the result.
  • The parameters of the backtracking algorithm are generally not fixed, and you can pass whatever you need.
  • Since it is a recursive algorithm, you need to pay attention to the termination conditions, which are generally required by the topic.
  • One of the cores of the backtracking algorithm is: state reset , that is, returning to the state before the operation.

Pseudocode implementation

void backtracking(路径,选择列表,结果集...) {
    if (终止条件) {
        存放结果操作;
        return;
    }

    for (i = start; i <= n && (剪枝操作); i++) { // 剪枝操作不强制要求有
        处理当前节点;
        backtracking(路径,选择列表,结果集...); // 递归
        状态重置,撤销处理结果
    }
}

Problem solving experience

  • To solve backtracking questions, remember that the backtracking template is the first mystery.
  • The backtracking algorithm is a depth-first traversal algorithm, a violent search algorithm, and a recursive algorithm.
  • Since the backtracking question is a depth-first search, the common optimization methods are actually pruning and memory search.
  • Because the backtracking algorithm is brute force, it is not very efficient, so don't choose it unless you have to.
  • When simply seeking the number of results, DP is generally used, but when the process is required, backtracking must be used. This is also a type of question, so pay attention.
  • In general, backtracking can be considered for combination problems, cutting problems, subset problems, permutation problems, and chessboard problems.
  • The problems that can be solved by the backtracking method must be abstracted into a tree structure.
  • Keep in mind the precautions in the backtracking template in the previous section.

algorithm topic

17. Alphabet combinations for phone numbers

insert image description here
Problem analysis: use the backtracking template to solve it.
code show as below:

/**
 * 回溯法
 */
class Solution {
    public List<String> letterCombinations(String digits) {
        // 结果集
        List<String> res = new ArrayList<>();
        // 若为空,直接返回
        if (digits == null || digits.length() == 0) {
            return res;
        }
        // 拼接字符串用StringBuilder
        StringBuilder temp = new StringBuilder();
        String[] numString = new String[]{"0", "1", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        // 迭代递归
        backTracking(digits, temp, numString, 0, res);
        return res;
    }

    public void backTracking(String digits, StringBuilder temp, String[] numString, int num, List<String> res) {
        // 递归出口
        if (num == digits.length()) {
            res.add(temp.toString());
            return;
        }
        String str = numString[digits.charAt(num) - '0'];
        for (int i = 0; i < str.length(); i++) {
            temp.append(str.charAt(i));
            backTracking(digits, temp, numString, num + 1, res);
            // 去掉最后一位,继续尝试
            temp.deleteCharAt(temp.length() - 1);
        }
    }
}

22. Parentheses generation

insert image description here
Problem analysis: use the backtracking template to solve it.
code show as below:

/**
 * 回溯法
 */
class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        generate(res, 0, 0, "", n);
        return res;
    }

    public void generate(List<String> res, int l, int r, String tmp, int n) {
        // 当字符串长度为 n*2 时,到达递归出口
        if (tmp.length() == n * 2) {
            res.add(tmp);
            return;
        }

        // 左括号数不能大于总括号数
        if (l < n) {
            generate(res, l + 1, r, tmp + "(", n);
        }

        // 右括号不能大于左括号数
        if (r < l) {
            generate(res, l, r + 1, tmp + ")", n);
        }
    }
}

37. Solve Sudoku

insert image description here

insert image description here
Problem analysis: It can be solved by backtracking template plus bit operation.
code show as below:

/**
 * 回溯 + 位运算
 */
class Solution {
    // 储存每一行存在的数字 
    private int[] line = new int[9];
    // 储存 每一列存在的数字
    private int[] column = new int[9];
    // 储存每一个 3*3 宫存在的数字 
    private int[][] block = new int[3][3];
    // 这个布尔数组用来判断是否填完所有空格
    private boolean valid = false;
    // 这个list用来记录所有空格的位置,整数数组第一个位置为行的位置 ,第二个位置为列的位置
    private List<int[]> spaces = new ArrayList<int[]>();

    public void solveSudoku(char[][] board) {
        // 遍历所有位置
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                // 如果位置为空,我们把该位置加入spaces中
                if (board[i][j] == '.') {
                    spaces.add(new int[]{i, j});
                } else {
                    // 不为空则把该数字分别纳入对应的行,列,3*3宫中
                    int digit = board[i][j] - '0' - 1;
                    flip(i, j, digit);
                }
            }
        }
        // 开始搜索
        dfs(board, 0);
    }

    public void dfs(char[][] board, int pos) {
        // 如果spaces被遍历完了,说明我们填完了空格,将valid改为true,通过return结束这个函数
        if (pos == spaces.size()) {
            valid = true;
            return;
        }
        // 获取第一个空格的位置 
        int[] space = spaces.get(pos);
        // i,j分别为该空格的行数和列数 
        int i = space[0], j = space[1];
        // |为or,通过3个或运算我们可以得到一个9位的二进制数字,从右到左分别代表1-9这个9个数是否可以填入该空格,通过~运算(取反),我们用1表示该数字是一个可填入的选项,0x1ff为十六进制 ,等同于111111111)
        int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
        // 当mask不为0并且 valid为false表示还有数待填入,继续这个循环,mask &= (mask - 1)把最低位的1变为0
        for (; mask != 0 && !valid; mask &= (mask - 1)) {
            // 这个操作只保留最低位的1
            int digitMask = mask & (-mask);
            // 最低位的1后面的位数,digitMask-1将原本1后面的0全部变为了1
            int digit = Integer.bitCount(digitMask - 1);
            // 更新行,列,宫
            flip(i, j, digit);
            // 把该数填入板中
            board[i][j] = (char) (digit + '0' + 1);
            // 继续搜索 
            dfs(board, pos + 1);
            // 撤回操作(原理是flip中的~运算,两个 1则为0,0表示这个位置代表的1-9中的那个数 不再是一个可被填入的选项)
            flip(i, j, digit);
        }
    }

    public void flip(int i, int j, int digit) {
        // ^代表异或,只有1个1的时候才为1。比如0011^1001=1010
        // <<代表左移,比如 1<<2=4被加入到下面的三个数组中,在二进制4为100,意味着3这个数字被占用了
        line[i] ^= (1 << digit);
        column[j] ^= (1 << digit);
        block[i / 3][j / 3] ^= (1 << digit);
    }
}

39. Combined sum

insert image description here
Problem analysis: use the backtracking template to solve it.
code show as below:

/**
 * 回溯法
 */
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        Arrays.sort(candidates);
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> temp = new ArrayList<>();
        helper(0,res,temp,candidates,0,target);
        return res;
    }

    private void helper(int curr,List<List<Integer>> res,List<Integer> temp,int[] candidates,int n,int target){
        
        // 1
        if(curr == target){
            res.add(new ArrayList<>(temp));
            return;
        }

        // 2
        for(int i = n;i<candidates.length;i++){
            if(curr + candidates[i] <= target){
                // 2.1
                temp.add(candidates[i]);
                // 2.2
                helper(curr + candidates[i],res,temp,candidates,n,target);
                // 2.3
                temp.remove(temp.size()-1);
            }else{
                break;
            }
            n++;         
        }
    }
}

40. Portfolio Sum II

insert image description here
Problem analysis: use the backtracking template to solve it.
code show as below:

/**
 * 回溯法
 */
class Solution {
    List<List<Integer>> list=new ArrayList<>();
    List<Integer> path=new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        dfs(candidates,target,0);
        return list;
    }
    private void dfs(int[] candidates, int target,int index){
        if(target==0){
            list.add(new ArrayList<>(path));
            return;
        }
        for(int i=index;i<candidates.length;i++){
            if(candidates[i]<=target){
                if(i>index&&candidates[i]==candidates[i-1]){
                    continue;
                }
                path.add(candidates[i]);
                dfs(candidates,target-candidates[i],i+1);
                path.remove(path.size()-1);
            }
        }
    }
}

46. ​​Full Arrangement

insert image description here
Problem analysis: use the backtracking template to solve it.
code show as below:

/**
 * 回溯法
 */
class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> list = new ArrayList<>();
        backtrack(res, list, nums);
        return res;        
    }
    
    public void backtrack(List<List<Integer>> res, List<Integer> list, int[] nums) {
        // 1
        if(list.size() == nums.length) {
            res.add(new ArrayList<Integer>(list));
            return;
        }
        // 2
        for(int num : nums) {
            if(!list.contains(num)) {
                // 2.1
                list.add(num);
                // 2.2
                backtrack(res, list, nums);
                // 2.3
                list.remove(list.size() - 1);
            }
        }
    }
}

47. Full Arrangement II

insert image description here
Topic analysis: use the backtracking template to solve the problem, the focus is on deduplication, and those who have passed through the same layer will not leave directly, skip it.
code show as below:

/**
 * 回溯法
 */
class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> path = new ArrayList<>();

    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used, false);
        Arrays.sort(nums);
        backTrack(nums, used);
        return result;
    }

    private void backTrack(int[] nums, boolean[] used) {
        // 1
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }
        // 2
        for (int i = 0; i < nums.length; i++) {
            // 与前数相等且used[i - 1] == true,说明同⼀树枝nums[i - 1]使⽤过
            // 与前数相等且used[i - 1] == false,说明同⼀树层nums[i - 1]使⽤过
            // 如果同⼀树层nums[i - 1]使⽤过则直接跳过,所有可能,因为之前跑过一次了
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
                continue;
            }
            // 如果同⼀树⽀nums[i]没使⽤过开始处理
            if (used[i] == false) {
                used[i] = true;// 标记同⼀树⽀nums[i]使⽤过,防止同一树支重复使用
                // 2.1
                path.add(nums[i]);
                // 2.2
                backTrack(nums, used);
                // 2.3
                path.remove(path.size() - 1);// 回溯,说明同⼀树层nums[i]使⽤过,防止下一树层重复
                used[i] = false;
            }
        }
    }
}

51. Queen N

insert image description here
Topic Analysis: Use set-based backtracking to solve problems.
code show as below:

/**
 * 回溯法
 */
class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> solutions = new ArrayList<List<String>>();
        int[] queens = new int[n];
        Arrays.fill(queens, -1);
        Set<Integer> columns = new HashSet<Integer>();
        Set<Integer> diagonals1 = new HashSet<Integer>();
        Set<Integer> diagonals2 = new HashSet<Integer>();
        backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);
        return solutions;
    }

    public void backtrack(List<List<String>> solutions, int[] queens, int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {
        if (row == n) {
            List<String> board = generateBoard(queens, n);
            solutions.add(board);
        } else {
            for (int i = 0; i < n; i++) {
                if (columns.contains(i)) {
                    continue;
                }
                int diagonal1 = row - i;
                if (diagonals1.contains(diagonal1)) {
                    continue;
                }
                int diagonal2 = row + i;
                if (diagonals2.contains(diagonal2)) {
                    continue;
                }
                queens[row] = i;
                columns.add(i);
                diagonals1.add(diagonal1);
                diagonals2.add(diagonal2);
                backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);
                queens[row] = -1;
                columns.remove(i);
                diagonals1.remove(diagonal1);
                diagonals2.remove(diagonal2);
            }
        }
    }

    public List<String> generateBoard(int[] queens, int n) {
        List<String> board = new ArrayList<String>();
        for (int i = 0; i < n; i++) {
            char[] row = new char[n];
            Arrays.fill(row, '.');
            row[queens[i]] = 'Q';
            board.add(new String(row));
        }
        return board;
    }
}

52. Empress N II

insert image description here
Topic Analysis: Use set-based backtracking to solve problems.
code show as below:

/**
 * 回溯法
 */
class Solution {
    public int totalNQueens(int n) {
        Set<Integer> columns = new HashSet<Integer>();
        Set<Integer> diagonals1 = new HashSet<Integer>();
        Set<Integer> diagonals2 = new HashSet<Integer>();
        return backtrack(n, 0, columns, diagonals1, diagonals2);
    }

    public int backtrack(int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {
        if (row == n) {
            return 1;
        } else {
            int count = 0;
            for (int i = 0; i < n; i++) {
                if (columns.contains(i)) {
                    continue;
                }
                int diagonal1 = row - i;
                if (diagonals1.contains(diagonal1)) {
                    continue;
                }
                int diagonal2 = row + i;
                if (diagonals2.contains(diagonal2)) {
                    continue;
                }
                columns.add(i);
                diagonals1.add(diagonal1);
                diagonals2.add(diagonal2);
                count += backtrack(n, row + 1, columns, diagonals1, diagonals2);
                columns.remove(i);
                diagonals1.remove(diagonal1);
                diagonals2.remove(diagonal2);
            }
            return count;
        }
    }
}

77. Combination

insert image description here
Problem analysis: use the backtracking template to solve it.
code show as below:

/**
 * 回溯法
 */
class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {
        combineHelper(n, k, 1);
        return result;
    }

    private void combineHelper(int n, int k, int startIndex){
        //终止条件
        // 1
        if (path.size() == k){
            result.add(new ArrayList<>(path));
            return;
        }
        //2
        for (int i = startIndex; i <= n - (k - path.size()) + 1; i++){
            // 2.1
            path.add(i);
            // 2.2
            combineHelper(n, k, i + 1);
            // 2.3
            path.removeLast();
        }
    }
}

78. Subset

insert image description here
Problem analysis: use the backtracking template to solve it.
code show as below:

/**
 * 回溯法
 */
class Solution {
    List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
    LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
    public List<List<Integer>> subsets(int[] nums) {
        if (nums.length == 0){
            result.add(new ArrayList<>());
            return result;
        }
        helper(nums, 0);
        return result;
    }

    private void helper(int[] nums, int startIndex){
        result.add(new ArrayList<>(path));// 遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合
        if (startIndex >= nums.length){ // 终止条件可不加
            return;
        }
        for (int i = startIndex; i < nums.length; i++){
            path.add(nums[i]);
            helper(nums, i + 1);
            path.removeLast();
        }
    }
}

79. Word Search

insert image description here
Problem analysis: use the backtracking template to solve it.
code show as below:

/**
 * 回溯法
 */
class Solution {

    public boolean exist(char[][] board, String word) {
        if (board == null) {
            return false;
        }

        // 该字母是否已遍历到
        boolean[][] used = new boolean[board.length][board[0].length];

        // 遍历所有坐标点,以作为起始点
        for (int row = 0; row < board.length; row++) {
            for (int column = 0; column < board[0].length; column++) {
                // 找到则立即返回
                if (helper(board, used, word, row, column, 0)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @param board  字母二维数组
     * @param used   该字母是否使用表
     * @param word   查找单词
     * @param row    当前字母所在行
     * @param column 当前字母所在列
     * @param index  当前字母所在索引
     * @return
     */
    public boolean helper(char[][] board, boolean[][] used, String word, int row, int column, int index) {

        // 超过边界、已使用过和与word不匹配,立即返回,不再进行其它操作
        if (row < 0 || row >= board.length || column < 0 || column >= board[0].length
                || used[row][column] || board[row][column] != word.charAt(index)) {
            return false;
        }

        // 1
        // 查到最后一个字母,即查到该单词
        if (index == word.length() - 1) {
            return true;
        }

        // 2
        // 2.1 添加状态
        used[row][column] = true;
        // 2.2 执行
        boolean res = helper(board, used, word, row + 1, column, index + 1)
                || helper(board, used, word, row - 1, column, index + 1)
                || helper(board, used, word, row, column + 1, index + 1)
                || helper(board, used, word, row, column - 1, index + 1);
        // 2.3 撤销状态
        used[row][column] = false;
        return res;
    }
}

90. Subset II

insert image description here
Topic analysis: Solve it with a backtracking template, and use a Boolean array to deduplicate.
code show as below:

/**
 * 回溯法
 */
class Solution {
    List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合
   LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
   boolean[] used;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        if (nums.length == 0){
            result.add(path);
            return result;
        }
        Arrays.sort(nums);
        used = new boolean[nums.length];
        subsetsWithDupHelper(nums, 0);
        return result;
    }
    
    private void subsetsWithDupHelper(int[] nums, int startIndex){
        result.add(new ArrayList<>(path));
        // 1
        if (startIndex >= nums.length){
            return;
        }
        // 2
        for (int i = startIndex; i < nums.length; i++){
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){
                continue;
            }
            // 2.1           
            path.add(nums[i]);
            used[i] = true;
            // 2.2
            subsetsWithDupHelper(nums, i + 1);
            // 2.3
            path.removeLast();
            used[i] = false;
        }
    }
}

93. Restoring IP addresses

insert image description here
Topic analysis: Solve it with a backtracking template. The difficulty here is that you need to abstract the question into a tree structure, and then use the backtracking method to solve it, which is difficult to think of.
code show as below:

/**
 * 回溯法
 */
 class Solution {
    List<String> result = new ArrayList<>();

    public List<String> restoreIpAddresses(String s) {
        if (s.length() > 12) return result; // 算是剪枝了
        backTrack(s, 0, 0);
        return result;
    }

    // startIndex: 搜索的起始位置, pointNum:添加逗点的数量
    private void backTrack(String s, int startIndex, int pointNum) {
        if (pointNum == 3) {// 逗点数量为3时,分隔结束
            // 判断第四段⼦字符串是否合法,如果合法就放进result中
            if (isValid(s,startIndex,s.length()-1)) {
                result.add(s);
            }
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            if (isValid(s, startIndex, i)) {
                s = s.substring(0, i + 1) + "." + s.substring(i + 1);    // 在str的后⾯插⼊⼀个逗点
                pointNum++;
                backTrack(s, i + 2, pointNum);// 插⼊逗点之后下⼀个⼦串的起始位置为i+2
                pointNum--;// 回溯
                s = s.substring(0, i + 1) + s.substring(i + 2);// 回溯删掉逗点
            } else {
                break;
            }
        }
    }

    // 判断字符串s在左闭⼜闭区间[start, end]所组成的数字是否合法
    private Boolean isValid(String s, int start, int end) {
        if (start > end) {
            return false;
        }
        if (s.charAt(start) == '0' && start != end) { // 0开头的数字不合法
            return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s.charAt(i) > '9' || s.charAt(i) < '0') { // 遇到⾮数字字符不合法
                return false;
            }
            num = num * 10 + (s.charAt(i) - '0');
            if (num > 255) { // 如果⼤于255了不合法
                return false;
            }
        }
        return true;
    }
}

95. Different Binary Search Trees II

insert image description here
Topic analysis: Simply asking for the number can use DP, but for the process you need to use backtracking. This is also a type of question, so pay attention.
code show as below:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
 /**
  * 回溯法
  */
class Solution {
    public List<TreeNode> generateTrees(int n) {
        return helper(1,n);
    }
    public List<TreeNode> helper(int lo, int hi){
        List<TreeNode> res=new LinkedList<>();
        // 1
        if(lo>hi){
            res.add(null);
            return res;
        }
        // 2
        for(int i = lo; i <= hi; i++){
            List<TreeNode> left = helper(lo, i-1);
            List<TreeNode> right = helper(i+1, hi);
            for(TreeNode le : left){
                for(TreeNode ri : right){
                    TreeNode root=new TreeNode(i);
                    root.left=le;
                    root.right=ri;
                    res.add(root);
                }
            }
        }
        return res;
    }
}

126. Word Solitaire II

insert image description here
Problem analysis: use the backtracking template to solve it.
code show as below:

/**
 * 回溯法
 */
class Solution {

    public List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList) {
        List<List<String>> res = new ArrayList<>();
        Set<String> dict = new HashSet<>(wordList);
        if (!dict.contains(endWord)) {
            return res;
        }
        dict.remove(beginWord);

        Map<String, Integer> steps = new HashMap<>();
        steps.put(beginWord, 0);
        Map<String, Set<String>> from = new HashMap<>();
        boolean found = bfs(beginWord, endWord, dict, steps, from);

        if (found) {
            Deque<String> path = new ArrayDeque<>();
            path.add(endWord);
            dfs(from, path, beginWord, endWord, res);
        }
        return res;
    }


    private boolean bfs(String beginWord, String endWord, Set<String> dict, Map<String, Integer> steps, Map<String, Set<String>> from) {
        int wordLen = beginWord.length();
        int step = 0;
        boolean found = false;

        Queue<String> queue = new LinkedList<>();
        queue.offer(beginWord);
        while (!queue.isEmpty()) {
            step++;
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                String currWord = queue.poll();
                char[] charArray = currWord.toCharArray();
                for (int j = 0; j < wordLen; j++) {
                    char origin = charArray[j];
                    for (char c = 'a'; c <= 'z'; c++) {
                        charArray[j] = c;
                        String nextWord = String.valueOf(charArray);
                        if (steps.containsKey(nextWord) && steps.get(nextWord) == step) {
                            from.get(nextWord).add(currWord);
                        }

                        if (!dict.contains(nextWord)) {
                            continue;
                        }
                        dict.remove(nextWord);
                        queue.offer(nextWord);
                        from.putIfAbsent(nextWord, new HashSet<>());
                        from.get(nextWord).add(currWord);
                        steps.put(nextWord, step);
                        if (nextWord.equals(endWord)) {
                            found = true;
                        }
                    }
                    charArray[j] = origin;
                }
            }
            if (found) {
                break;
            }
        }
        return found;
    }

    private void dfs(Map<String, Set<String>> from, Deque<String> path, String beginWord, String cur, List<List<String>> res) {
        if (cur.equals(beginWord)) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (String precursor : from.get(cur)) {
            path.addFirst(precursor);
            dfs(from, path, beginWord, precursor, res);
            path.removeFirst();
        }
    }
}

131. Split Palindrome

insert image description here
Topic analysis: Solve the problem with the backtracking template, and the cutting problem can be abstracted into a combination problem, which is also difficult to think of.
code show as below:

/**
 * 回溯法
 */
 class Solution {
    List<List<String>> lists = new ArrayList<>();
    Deque<String> deque = new LinkedList<>();

    public List<List<String>> partition(String s) {
        backTracking(s, 0);
        return lists;
    }

    private void backTracking(String s, int startIndex) {
        // 如果起始位置大于s的大小,说明找到了一组分割方案
        if (startIndex >= s.length()) {
            lists.add(new ArrayList(deque));
            return;
        }
        for (int i = startIndex; i < s.length(); i++) {
            // 如果是回文子串,则记录
            if (isPalindrome(s, startIndex, i)) {
                String str = s.substring(startIndex, i + 1);
                deque.addLast(str);
            } else {
                continue;
            }
            // 起始位置后移,保证不重复
            backTracking(s, i + 1);
            deque.removeLast();
        }
    }
    // 判断是否是回文串
    private boolean isPalindrome(String s, int startIndex, int end) {
        for (int i = startIndex, j = end; i < j; i++, j--) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
        }
        return true;
    }
}

216. Combined Sum III

insert image description here
Problem analysis: use the backtracking template to solve it.
code show as below:

/**
 * 回溯法
 */
class Solution {
	List<List<Integer>> result = new ArrayList<>();
	LinkedList<Integer> path = new LinkedList<>();

	public List<List<Integer>> combinationSum3(int k, int n) {
		helper(n, k, 1, 0);
		return result;
	}

	private void helper(int targetSum, int k, int startIndex, int sum) {
		// 减枝
		if (sum > targetSum) {
			return;
		}

        // 1
		if (path.size() == k) {
			if (sum == targetSum) result.add(new ArrayList<>(path));
			return;
		}
		
		// 减枝 9 - (k - path.size()) + 1
        //2
		for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
            //2.1
			path.add(i);
			sum += i;
			helper(targetSum, k, i + 1, sum);
            // 2.2
			//回溯
			path.removeLast();
			//回溯
			sum -= i;
		}
	}
}

282. Add operators to expressions

insert image description here
Problem analysis: use the backtracking template to solve it.
code show as below:

/**
 * 回溯法
 */
class Solution {
    int n;
    String num;
    int target;
    List<String> ans;

    public List<String> addOperators(String num, int target) {
        this.n = num.length();
        this.num = num;
        this.target = target;
        this.ans = new ArrayList<String>();
        StringBuffer expr = new StringBuffer();
        backtrack(expr, 0, 0, 0);
        return ans;
    }

    public void backtrack(StringBuffer expr, int i, long res, long mul) {
        if (i == n) {
            if (res == target) {
                ans.add(expr.toString());
            }
            return;
        }
        int signIndex = expr.length();
        if (i > 0) {
            expr.append(0); // 占位,下面填充符号
        }
        long val = 0;
        // 枚举截取的数字长度(取多少位),注意数字可以是单个 0 但不能有前导零
        for (int j = i; j < n && (j == i || num.charAt(i) != '0'); ++j) {
            val = val * 10 + num.charAt(j) - '0';
            expr.append(num.charAt(j));
            if (i == 0) { // 表达式开头不能添加符号
                backtrack(expr, j + 1, val, val);
            } else { // 枚举符号
                expr.setCharAt(signIndex, '+');
                backtrack(expr, j + 1, res + val, val);
                expr.setCharAt(signIndex, '-');
                backtrack(expr, j + 1, res - val, -val);
                expr.setCharAt(signIndex, '*');
                backtrack(expr, j + 1, res - mul + mul * val, mul * val);
            }
        }
        expr.setLength(signIndex);
    }
}

back to the homepage

Some feelings about brushing Leetcode 500+ questions

Next

Greedy in "Algorithm Series"

Guess you like

Origin blog.csdn.net/qq_22136439/article/details/126683802