Application of Backtracking Algorithm

template

void backtracking(参数) {
    
    
    if (终止条件) {
    
    
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
    
    
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

1. Combination

topic description

link link

Given two integers nand k, [1, n]return all possible kcombinations of numbers in the range .

You can return answers in any order.

Example 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

the code

class Solution {
    
    
    //定义路径
    LinkedList<Integer> path = new LinkedList<>();
    //返回结果
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
    
    
        combineHelper(n, k, 1);
        return res;
    }

     /**
     * 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex
     * @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
     */
    private void combineHelper(int n, int k, int startIndex){
    
    
         //设置终止条件:收集完节点就可以
        if(path.size() == k){
    
    
            res.add(new ArrayList<>(path));
            return;
        }

        //回溯中的循环遍历
        for(int i = startIndex;i <= n;i++){
    
    
            //加入到路径
            path.add(i);
            combineHelper(n, k, i + 1);
            path.removeLast();
        }
    }
}

Code optimization (pruning)

class Solution {
    
    
    //定义路径
    LinkedList<Integer> path = new LinkedList<>();
    //返回结果
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
    
    
        combineHelper(n, k, 1);
        return res;
    }

     /**
     * 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex
     * @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
     */
    private void combineHelper(int n, int k, int startIndex){
    
    
         //设置终止条件:收集完节点就可以
        if(path.size() == k){
    
    
            res.add(new ArrayList<>(path));
            return;
        }

        //回溯中的循环遍历
        /*  
            此处可以优化,回溯算法的优化大都在控制单层循环的次数上
            n - (k - path.size()) + 1:表示可以向下递归所需的最小剩余数组长度
            例如:n = 4,k = 4
            只有1234,所以当 startIndex = 2,只能遍历到234
         */
        for(int i = startIndex;i <= n - (k - path.size()) + 1;i++){
    
    
            //加入到路径
            path.add(i);
            combineHelper(n, k, i + 1);
            path.removeLast();
        }
    }
}

2. Combined sum

1. Combined sum (1)

topic description

link link

Give you oneno repeating elementsThe integer array candidates and a target integer target, find out all the different combinations of the numbers in the candidates that can make the sum of the target number target, and return it in the form of a list. You can return these combinations in any order.

The same number in candidates can be selected repeatedly without limit . Two combinations are different if the chosen number of at least one number is different.

For a given input, the number of different combinations that sum to target is guaranteed to be less than 150.

Example 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
23 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

the code

class Solution {
    
    
    //创建结果集
    List<List<Integer>> res = new ArrayList<>();
    //路径
    LinkedList<Integer> path = new LinkedList<>();
    //记录和
    int sum = 0;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
    
    
        //创建数组
        int n = candidates.length;
        combinationSumHelp(candidates, target, 0, n);
        return res;
    }

    public void combinationSumHelp(int[] candidates, int target,int startIndex,int n){
    
    
        // 减枝
		if (sum > target) {
    
    
			return;
		}
        //结束条件
        if(sum == target){
    
    
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = startIndex;i < n;i++){
    
    
            path.add(candidates[i]);
            sum += candidates[i];
            //由于元素可以无限制被取,所以不用i + 1
            combinationSumHelp(candidates, target, i, n);
            sum -= candidates[i];
            path.removeLast();
        }
    }
}

2. Combined sum (2)

topic description

link link

Given a set of candidate numbers candidates and a target number target, find out all the combinations in candidates that can make the sum of numbers into target.

Each number in candidates can only be used once in each combination .

Note: A solution set cannot contain duplicate combinations.

Example 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

the code

class Solution {
    
    
    //创建结果集
    List<List<Integer>> res = new ArrayList<>();
    //路径
    LinkedList<Integer> path = new LinkedList<>();
    //记录和
    int sum = 0;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    
    
        //创建数组
        int n = candidates.length;
        //为了将重复的数字都放到一起,所以先进行排序
        Arrays.sort(candidates);
        combinationSumHelp(candidates, target, 0, n);
        return res;
    }

    public void combinationSumHelp(int[] candidates, int target,int startIndex,int n){
    
    
        // // 减枝
		// if (sum > target) {
    
    
		// 	return;
		// }
        //结束条件
        if(sum == target){
    
    
            res.add(new ArrayList<>(path));
            return;
        }
        //sum + candidates[i] <= target:可以避免单层不必要的遍历,加在这里效率较高,和下面代码效果类似
        /*
        	// // 减枝
            // if (sum > target) {
            // 	return;
            // }
        */
        for(int i = startIndex;i < n && sum + candidates[i] <= target;i++){
    
    
            //正确剔除重复解的办法
            //跳过同一树层使用过的元素
            if ( i > startIndex && candidates[i] == candidates[i - 1] ) {
    
    
                continue;
            }
            path.add(candidates[i]);
            sum += candidates[i];
            //i + 1:取不同元素
            combinationSumHelp(candidates, target, i + 1, n);
            sum -= candidates[i];
            path.removeLast();
        }
    }
}

3. Combined sum (3)

topic description

link link

Find all combinations of k numbers whose sum is n, and satisfy the following conditions:

Use only the digits 1 to 9
Use each digit at most once
Return a list of all possible valid combinations. The list cannot contain the same combination twice, and the combinations may be returned in any order.

Example 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

the code

class Solution {
    
    
     //定义路径
    LinkedList<Integer> path = new LinkedList<>();
    //返回结果
    List<List<Integer>> res = new ArrayList<>();
    //记录和
    int sum = 0;
    public List<List<Integer>> combinationSum3(int k, int n) {
    
    
        combineHelper(n, k, 1);
        return res;
    }

     /**
     * 每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围,就是要靠startIndex
     * @param startIndex 用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
     */
    private void combineHelper(int n, int k, int startIndex){
    
    
        // 减枝
		if (sum > n) {
    
    
			return;
		}
         //设置终止条件:收集完节点就可以
        if(path.size() == k){
    
    
            if(sum == n)
            res.add(new ArrayList<>(path));
            return;
        }

        //回溯中的循环遍历
        /*  
            此处可以优化,回溯算法的优化大都在控制单层循环的次数上
            n - (k - path.size()) + 1:表示可以向下递归所需的最小剩余数组长度
            例如:n = 4,k = 4
            只有1234,所以当 startIndex = 2,只能遍历到234
         */
        for(int i = startIndex;i <= 9 - (k - path.size()) + 1;i++){
    
    
            //加入到路径
            sum += i;
            path.add(i);
            combineHelper(n, k, i + 1);
            path.removeLast();
            sum -= i;
        }
    }
}

4. Summary

  1. The backtracking code template for the sum of the three combinations is similar to
  2. It is mainly to deduplicate the result set ** (should be sorted before deduplication) ** and backtracking process pruning

3. Subset problem

1. Split the palindrome string

topic description

link link

Given a string s, please sdivide into some substrings so that each substring is a palindrome . Returns sall possible splitting schemes. A palindrome is a string that reads the same both forwards and backwards.

Example 1:

输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

the code

class Solution {
    
    
    //结果集
    List<List<String>> res = new ArrayList<>();
    //栈
    Deque<String> stack = new LinkedList<>();
    public List<List<String>> partition(String s) {
    
    
        int n = s.length();
        partitionHelp(s, 0, n);
        return res;
    }

    public void partitionHelp(String s,int startIndex,int n){
    
    
        //终止条件,如果起始位置大于s的大小,说明找到了一组分割方案
        if(startIndex >= n){
    
    
            res.add(new ArrayList<>(stack));
            return;
        }

        //单层循环
        for(int i = startIndex;i < n;i++){
    
    
            //判断是不是回文串
            if(isPalindrome(s, startIndex, i)){
    
    
                String str = s.substring(startIndex, i + 1);
                stack.addLast(str);
            }else{
    
    
                continue;
            }
            //起始位置后移,保证不重复
            partitionHelp(s, i + 1, n);
            stack.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;
    }
}

2. Restoring the IP address

topic description

link link

A valid IP address consists of exactly four integers (each integer between 0 and 255, and cannot contain leading 0s), separated by '.'.

For example: "0.1.2.201" and "192.168.1.1" are valid IP addresses, but "0.011.255.245", "192.168.1.312" and "[email protected]" are invalid IP addresses.
Given a string s containing only numbers representing an IP address, return all possible valid IP addresses that can be formed by inserting '.' into s. You cannot reorder or delete any numbers in s. You can return answers in any order.

the code

class Solution {
    
    
    //定义路径
    LinkedList<String> stack = new LinkedList<>();
    //返回结果
    List<String> res = new ArrayList<>();
    StringBuilder sb = new StringBuilder();

    public List<String> restoreIpAddresses(String s) {
    
    
        int n = s.length();
        restoreIpAddressesHelp(s, 0, n);
        return res;
    }

     public void restoreIpAddressesHelp(String s,int startIndex,int n){
    
    
         
        //终止条件,如果起始位置大于s的大小,说明找到了一组分割方案
        if(startIndex >= n){
    
    
            if(stack.size() == 4){
    
    
                sb.append(stack.get(0));
                sb.append(".");
                sb.append(stack.get(1));
                sb.append(".");
                sb.append(stack.get(2));
                sb.append(".");
                sb.append(stack.get(3));
                res.add(sb.substring(0, sb.length()));
                sb.delete(0,sb.length());
            }
            return;
        }

        //单层循环
        for(int i = startIndex;i < n && stack.size() <= 4;i++){
    
    
            String str = s.substring(startIndex, i + 1);
            //判断数字是不是符合要求
            if(isValid(str)){
    
                
                stack.add(str);
            }else{
    
    
                continue;
            }
            //起始位置后移,保证不重复
            restoreIpAddressesHelp(s, i + 1, n);
            stack.removeLast();
        }

    }

    //判断是否是是 0-255
    private boolean isValid(String str) {
    
    
        if(str.charAt(0) == '0' && str.length() > 1) return false;
        long num = Long.parseLong(str);
        if(num >= 0 && num <= 255) return true;
        else return false;
    }
}

3. Subset

topic description

link link

You are given an array of integers nums, the elements in the array are different from each other . Returns all possible subsets (power sets) of this array.

A solution set cannot contain duplicate subsets. You can return solution sets in any order.

Example 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

the code

class Solution {
    
    

    List<List<Integer>> res = new ArrayList<>();

    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> subsets(int[] nums) {
    
    
        //空集是所有集合的子集
        res.add(new ArrayList(0));
        int n = nums.length;
        subsetsHelp(nums, 0, n);
        return res;
    }

    public void subsetsHelp(int[] nums, int startIndex, int n) {
    
    
        //终止条件:如果起始位置等于nums的大小,说明找到了一组
        if(path.size() >= n){
    
    //终止条件可不加 也可以为 if (startIndex >= nums.length)
            return;
        }
        for(int i = startIndex;i < n;i++){
    
    
            path.add(nums[i]);
            res.add(new ArrayList<>(path));
            subsetsHelp(nums, i + 1, n);
            path.removeLast();
        }
    }
}

4. Subset II (Deduplication)

topic description

link link

Given an integer array nums, which may contain repeated elements, please return all possible subsets (power sets) of the array.

A solution set cannot contain duplicate subsets. In the returned solution set, the subsets can be in any order.

Example 1:

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

the code

class Solution {
    
    
     List<List<Integer>> res = new ArrayList<>();

    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
    
    
        //空集是所有集合的子集
        res.add(new ArrayList(0));
        //1. 排序
        Arrays.sort(nums);
        int n = nums.length;
        subsetsWithDupHelp(nums, 0, n);
        return res;
    }

     public void subsetsWithDupHelp(int[] nums, int startIndex, int n) {
    
    
        //终止条件:如果起始位置等于nums的大小,说明找到了一组
        if(path.size() >= n){
    
    //终止条件可不加 也可以为 if (startIndex >= nums.length)
            return;
        }
        for(int i = startIndex;i < n;i++){
    
    
            //2. 去除重复元素
            if ( i > startIndex && nums[i] == nums[i - 1] ) {
    
    
                continue;
            }
            path.add(nums[i]);
            res.add(new ArrayList<>(path));
            subsetsWithDupHelp(nums, i + 1, n);
            path.removeLast();
        }
    }
}

4. Full Arrangement

1. Full arrangement

topic description

link link

Given an array with no duplicate numbers nums, return all . You can return answers in any order.

Example 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

the code

class Solution {
    
    

    //结果集
    List<List<Integer>> res = new ArrayList<>();
    //路径
    LinkedList<Integer> path = new LinkedList<>();
    //定义访问数组,记录该元素是否被访问
    boolean[] visit;
    public List<List<Integer>> permute(int[] nums) {
    
    
        int n = nums.length;
        visit = new boolean[n];
        permuteHelp(nums, n);
        return res;
    }

    public void permuteHelp(int[] nums, int n){
    
    
        //返回条件:path中收集了n个元素后返回
        if(path.size() == n){
    
    
            res.add(new ArrayList<>(path));
            return;
        }
        
        for(int i = 0;i < n;i++){
    
    
            //访问,跳过
            if(visit[i]) continue;
            path.add(nums[i]);
            visit[i] = true;
            //回溯
            permuteHelp(nums, n);
            path.removeLast();
            visit[i] = false;
        }
    }
}

2. Full Arrangement II

topic description

link link

Given a sequence that may contain repeating numbers nums, return all non-repeating full permutations in any order .

Example 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
 [1,2,1],
 [2,1,1]]

the code

class Solution {
    
    

    //结果集
    List<List<Integer>> res = new ArrayList<>();
    //路径
    LinkedList<Integer> path = new LinkedList<>();
    //定义访问数组,记录该元素是否被访问
    boolean[] visit;
    public List<List<Integer>> permuteUnique(int[] nums) {
    
    
        int n = nums.length;
        visit = new boolean[n];
        Arrays.fill(visit, false);
        Arrays.sort(nums);
        permuteUniqueHelp(nums, n);
        return res;
    }

    public void permuteUniqueHelp(int[] nums, int n){
    
    
        //返回条件:path中收集了n个元素后返回
        if(path.size() == n){
    
    
            res.add(new ArrayList<>(path));
            return;
        }
        
        for(int i = 0;i < n;i++){
    
    
            // visit[i - 1] == true,说明同⼀树⽀visit[i - 1]使⽤过
            // visit[i - 1] == false,说明同⼀树层visit[i - 1]使⽤过
            // 如果同⼀树层nums[i - 1]使⽤过则直接跳过
            if (i > 0 && nums[i] == nums[i - 1] && visit[i - 1] == false) {
    
    
                continue;
            }
            //如果同⼀树⽀nums[i]没使⽤过开始处理
            if (visit[i] == false) {
    
    
                visit[i] = true;
                path.add(nums[i]);
                //回溯
                permuteUniqueHelp(nums, n);
                path.removeLast();
                visit[i] = false;
            }
        }
	}
}

5. N queens problem

topic

topic link

According to the rules of chess, a queen can attack a piece that is on the same row or column or on the same diagonal as the queen.

The n queen problem studies how to place nn queens n×non the chessboard so that the queens cannot attack each other.

Given an integer n, return all different solutions to the n-queens problem .

Each solution consists of a different pawn placement scheme for the n-queens problem , where 'Q'and '.'represent the queen and the open space, respectively.

Example 1:

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

analyze

the code

class Solution {
    
    

    //定义返回值
    List<List<String>> res = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
    
    
        //回溯棋盘
        char[][] chessboard = new char[n][n];
        for (char[] c : chessboard) {
    
    
            Arrays.fill(c, '.');
        }
        solveNQueensHelp(n, 0, chessboard);
        return res;  
    }

    public void solveNQueensHelp(int n, int row, char[][] chessboard){
    
    
        //结束条件
        if(row == n){
    
    
            res.add(Array2List(chessboard));
            return;
        }

        //遍历本层
        for(int j = 0;j < n;j++){
    
    
            if(isValid (row, j, n, chessboard)){
    
    
                chessboard[row][j] = 'Q';
                solveNQueensHelp(n, row + 1, chessboard);
                chessboard[row][j] = '.';  
            }
        }
    }
    
    //将二维数组转化为List<String> list = new ArrayList<>()
    public List Array2List(char[][] chessboard) {
    
    
        List<String> list = new ArrayList<>();

        for (char[] c : chessboard) {
    
    
            list.add(String.copyValueOf(c));
        }
        return list;
    }

    //判断棋盘是否符合
    public boolean isValid (int row, int col, int n, char[][] chessboard){
    
    
        // 检查列
        for (int i = 0; i < row; ++i) {
    
     // 相当于剪枝
            if (chessboard[i][col] == 'Q') {
    
    
                return false;
            }
        }

        // 检查45度对角线
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
    
    
            if (chessboard[i][j] == 'Q') {
    
    
                return false;
            }
        }

        // 检查135度对角线
        for (int i = row - 1, j = col + 1; i >= 0 && j <= n - 1; i--, j++) {
    
    
            if (chessboard[i][j] == 'Q') {
    
    
                return false;
            }
        }
        return true;

    }

}

6. Solve Sudoku

topic

topic link

Write a program that solves Sudoku problems by filling in the blanks.

The solution of Sudoku needs to follow the following rules :

  1. 1-9Numbers can only appear once per line.
  2. Numbers can only appear once 1-9in each column.
  3. 1-9Numbers 3x3can only appear once in each palace separated by a thick solid line. (Please refer to the example picture)

Numbers have been filled in the blanks of the Sudoku part, and the blanks are '.'indicated .

Example 1:

输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:

analyze

the code

class Solution {
    
    
    public void solveSudoku(char[][] board) {
    
    
        solveSudokuHelper(board);
    }

    private boolean solveSudokuHelper(char[][] board){
    
    
        //「一个for循环遍历棋盘的行,一个for循环遍历棋盘的列,
        // 一行一列确定下来之后,递归遍历这个位置放9个数字的可能性!」
         for (int i = 0; i < 9; i++){
    
     // 遍历行
            for (int j = 0; j < 9; j++){
    
     // 遍历列
                if (board[i][j] != '.'){
    
     // 跳过原始数字
                    continue;
                }
                for (char k = '1'; k <= '9'; k++){
    
     // (i, j) 这个位置放k是否合适
                    if (isValidSudoku(i, j, k, board)){
    
    
                        board[i][j] = k;
                        if (solveSudokuHelper(board)){
    
     // 如果找到合适一组立刻返回
                            return true;
                        }
                        board[i][j] = '.';//回溯
                    }
                }
                // 9个数都试完了,都不行,那么就返回false
                return false;
                 // 因为如果一行一列确定下来了,这里尝试了9个数都不行,说明这个棋盘找不到解决数独问题的解!
                // 那么会直接返回, 「这也就是为什么没有终止条件也不会永远填不满棋盘而无限递归下去!」
             }
        }
        // 遍历完没有返回false,说明找到了合适棋盘位置了
        return true;
    }
    /**
     * 判断棋盘是否合法有如下三个维度:
     *     同行是否重复
     *     同列是否重复
     *     9宫格里是否重复
     */
    private boolean isValidSudoku(int row, int col, char val, char[][] board){
    
    
        // 同行是否重复
        for (int i = 0; i < 9; i++){
    
    
            if (board[row][i] == val){
    
    
                return false;
            }
        }
        // 同列是否重复
        for (int j = 0; j < 9; j++){
    
    
            if (board[j][col] == val){
    
    
                return false;
            }
        }
        // 9宫格里是否重复
        int startRow = (row / 3) * 3;
        int startCol = (col / 3) * 3;
        for (int i = startRow; i < startRow + 3; i++){
    
    
            for (int j = startCol; j < startCol + 3; j++){
    
    
                if (board[i][j] == val){
    
    
                    return false;
                }
            }
        }
        return true;
    }
}

reference

code caprice

Guess you like

Origin blog.csdn.net/qq_42130468/article/details/127729086