回溯算法总结

解题的步骤是:先画图,再编码。去思考可以剪枝的条件, 为什么有的时候用 used 数组,有的时候设置搜索起点 begin 变量,理解状态变量设计的想法。

本序列参考资料

1.回溯算法入门级详解 + 练习
2.回溯法总结
3.
回溯算法主要有排列、组合、子集相关问题,Flood Fill,字符串中的回溯问题,游戏问题四大题型。

题型一:排列、组合、子集相关问题

题目一:全排列(46. 全排列

代码如下:

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        if (null == nums || 0 == len) {
            return res;
        }
        // 栈的实现,java语言要求
        Deque<Integer> path = new ArrayDeque<>();
        // 标记数字是否有使用过,默认为false没有使用过
        boolean[] used = new boolean[len];
        dfs(nums, len, 0, path, used, res);
        return res;
    }
    // 题目要求所有的搜索结果,所有找到一个结果返回上一层后还可以继续找下一个结果。如果题目要求只要一个结果,dfs可以返回boolean
    // 在一些地方可以及时退出
    private void dfs(int[] nums, int len, int depth, Deque<Integer> path, boolean[] used, List<List<Integer>> res) {
        // 递归结束的条件,depth从0开始,只要等于nums长度即可结束递归
        if (depth == len) {
            // path保存的是一个引用,dfs结束后,path内没有任何元素,所以每次用res保存结果时必须新建一个path的副本(即new                     // ArrayList(path))
            res.add(new ArrayList(path));
            return;
        }

        for (int i = 0; i < len; i ++) {
            if (used[i] == false) {
                used[i] = true;
                // 往栈中添加一个元素
                path.addLast(nums[i]);
                dfs(nums, len, depth + 1, path, used, res);
                // 删除栈中一个元素
                path.removeLast();
                used[i] = false;
            }
        }
    }
}

题目二:全排列(47. 全排列 II

题解:给出的序列中数字有重复的,所以要考虑剪枝。

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        if (null == nums || 0 == len) {
            return res;
        }
        // 剪枝的前提,排序后可以剪枝
        Arrays.sort(nums);
        Deque<Integer> path = new ArrayDeque<>();
        boolean[] used = new boolean[len];
        dfs(nums, len, 0, path, used, res);
        return res;
    }
    private void dfs(int[] nums, int len, int depth, Deque<Integer> path, boolean[] used, List<List<Integer>> res) {
        if (depth == len) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < len; i ++) {
            if (used[i]) {
                continue;
            }
            // 剪枝
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
                continue;
            }
            used[i] = true;
            path.addLast(nums[i]);
            dfs(nums, len, depth + 1, path, used, res);
            used[i] = false;
            path.removeLast();
        }
    }
}

题目三:组合总和(39. 组合总和

题解:1.由于给出的序列,可以无限制重复被选取。所以不能使用boolean used[]数组来标记给出的序列是否使用过。2.题目要求解集不能包含重复的组合。所以用begin指针标记使用的数字从哪里开始。即不能使用当前序列数字之前的数字。
代码如下:

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        int len = candidates.length;
        List<List<Integer>> res = new ArrayList<>();
        if (0 == len) {
            return res;
        }
        // 为了剪枝让给定的序列排序
        Arrays.sort(candidates);
        Deque<Integer> path = new ArrayDeque<>();
        dfs(candidates, 0, target, path, res);
        return res;
    }
    // 由于给定的序列数字可以无限制重复被选取,所以不用boolean used[]数组。
    private void dfs(int[] candidates, int begin, int target, Deque<Integer> path, List<List<Integer>> res) {
        if (0 == target) {
            res.add(new ArrayList<>(path));
            return;
        }
        // 解集不能包含重复的组合,所以选取下一个数字是必须从当前数字往后尝试
        for (int i = begin; i < candidates.length; i ++) {
            // 剪枝,由于已经排好序(从小到大),所以只要target < candidates[i]即可跳出循环
            if (target - candidates[i] < 0) {
                break;
            }
            path.addLast(candidates[i]);
            // 由于给定的序列数字可以无限制重复被选取,所以下一个数字还可以选取当前这个数字
            dfs(candidates, i, target - candidates[i], path, res);
            path.removeLast();
        }
    }
}

题目四:组合总和(40. 组合总和 II

代码如下:

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        int len = candidates.length;
        List<List<Integer>> res = new ArrayList<>();
        if (null == candidates || 0 == len || target < 0) {
            return res;
        }
        Arrays.sort(candidates);
        Deque<Integer> path = new ArrayDeque<>();
        dfs(candidates, 0, target, path, res);
        return res;
    }
    private void dfs(int[] candidates, int begin, int target, Deque<Integer> path, List<List<Integer>> res) {
        if (0 == target) {
            res.add(new ArrayList<>(path));
            return;
        }
        
        for (int i = begin; i < candidates.length; i ++) {
            // 大剪枝
            if (target - candidates[i] < 0) {
                break;
            }
            // 小剪枝
            if (i > begin && candidates[i] == candidates[i - 1]) {
                continue;
            }
            path.addLast(candidates[i]);
            dfs(candidates, i + 1, target - candidates[i], path, res);
            path.removeLast();
        }
    }
    }

总结:

1.题目要求解集不能重复,应当使用begin变量标记每一层所要遍历的序列。
2.题目要求解集可以重复,每一层遍历序列可以全部遍历。
3.题目要求给出的序列数字可以无限制重复被选取。进入下一层时深度(depth)依然是从当前数字开始搜索。即dfs(candidates, i, target - candidates[i], path, res);
4.题目要求给出的序列每个数字在每个组合中只能使用一次。进入下一层时深度(depth)应当是下一个数字开始。即dfs(candidates, len, i + 1, target - candidates[i], path, res);
5.剪枝应当要对给出的序列进行排序。

题目五:组合(77. 组合

代码如下:

class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<List<Integer>> res = new ArrayList<>();
        Deque<Integer> path = new ArrayDeque<>();
        dfs(n, 0, k, 1, path, res);
        return res;
    }
    private void dfs(int n, int depth, int k, int begin, Deque<Integer> path, List<List<Integer>> res) {
        if (depth == k) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = begin; i <= n; i ++) {
            path.addLast(i);
            dfs(n, depth + 1, k, i + 1, path, res);
            path.removeLast();
        }
    }
}

题目六:子集(78. 子集

代码如下:

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        res.add(new ArrayList<>());
        if (null == nums || 0 == len) {
            return res;
        }
        Deque<Integer> path = new ArrayDeque<>();
        for (int i = 1; i <= len; i ++) {
            dfs(nums, 0, i, 0, path, res);
        }
        return res;
    }
    private void dfs(int[] nums, int depth, int k, int begin, Deque<Integer> path, List<List<Integer>> res) {
        if (depth == k) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = begin; i < nums.length; i ++) {
            path.addLast(nums[i]);
            dfs(nums, depth + 1, k, i + 1, path, res);
            path.removeLast();
        }
    }
}

题目七:子集(90. 子集 II

代码如下:

 public List<List<Integer>> subsetsWithDup(int[] nums) {
        int len = nums.length;
        List<List<Integer>> res = new ArrayList<>();
        if (null == nums || 0 == len) {
            return res;
        }
        // 剪枝的前提:排序
        Arrays.sort(nums);
        Deque<Integer> path = new ArrayDeque<>();
        for (int i = 0; i <= nums.length; i ++) {
            dfs(nums, 0, 0, i, path, res);
        }
        return res;
    }
    private void dfs(int[] nums, int depth, int begin, int k, Deque<Integer> path, List<List<Integer>> res) {
        if (depth == k) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = begin; i < nums.length; i ++) {
            if (i > begin && nums[i] == nums[i - 1]) {
                continue;
            }
            path.addLast(nums[i]);
            dfs(nums, depth + 1, i + 1, k, path, res);
            path.removeLast();
        }
    }

题目八:第k个排列(60. 第k个排列

题解:1.题目没有要求题目要求解集可以重复,每一层遍历序列可以全部遍历。
代码如下:

class Solution {
    private int sum = 0;
    public String getPermutation(int n, int k) {
       int[] nums = new int[n];
       boolean[] used = new boolean[n];
       Deque<Integer> path = new ArrayDeque<>();
       for (int i = 0; i < n; i ++) {
           nums[i] = i + 1;
       }
       dfs(nums, 0, used, path, k);
       List<Integer> list = new ArrayList<>(path);
       StringBuilder sb = new StringBuilder();
       for (int i : list) {
           sb.append(i + "");
       }
       return sb.toString();
    }
    // 解法一:
    // private void dfs(int[] nums, int depth, boolean[] used, Deque<Integer> path, int k) {
    //     if (depth == nums.length) {
    //         sum ++;
    //         return;
    //     }
    //     for (int i = 0; i < nums.length; i ++) {
    //         if (used[i]) {
    //             continue;
    //         }
    //         used[i] = true;
    //         path.addLast(nums[i]);
    //         dfs(nums, depth + 1, used, path, k);
    //         if (sum == k) {
    //             return;
    //         }
    //         used[i] = false;
    //         path.removeLast();
    //     }
    // }
    // 解法二:
     private boolean dfs(int[] nums, int depth, boolean[] used, Deque<Integer> path, int k) {
        if (depth == nums.length) {
            sum ++;
            return sum == k;
        }
        for (int i = 0; i < nums.length; i ++) {
            if (used[i]) {
                continue;
            }
            used[i] = true;
            path.addLast(nums[i]);
            if (dfs(nums, depth + 1, used, path, k)) {
                return true;
            }
            used[i] = false;
            path.removeLast();
        }
        return false;
    }
}

题型二:Flood Fill

Flood 是「洪水」的意思,Flood Fill 直译是「泛洪填充」的意思,体现了洪水能够从一点开始,迅速填满当前位置附近的地势低的区域。
这一类题目的dfs方法模板:
1.递归出口条件判断
2.标记
3.走不通的选择

题目一:图像渲染(733. 图像渲染)

代码如下:

class Solution {
    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
        // 注意:假如一开始的值和newColor一样,直接返回结果,不然会出现死循环
        if (image[sr][sc] == newColor) {
            return image;
        }
        int value = image[sr][sc];
        boolean[][] used = new boolean[image.length][image[0].length];
        dfs(image, sr, sc, newColor, value, used);
        return image;
    }
    private void dfs(int[][] image, int i, int j, int newColor, int value, boolean[][] used) {
        // 递归出口条件判断
        if (i < 0 || i >= image.length || j < 0 || j >= image[0].length || image[i][j] != value || used[i][j]) {
            return;
        }
        // 标记
        used[i][j] = true;
        image[i][j] = newColor;
        // 走不通的方向路径
        dfs(image, i - 1, j, newColor, value, used);
        dfs(image, i, j + 1, newColor, value, used);
        dfs(image, i + 1, j, newColor, value, used);
        dfs(image, i, j - 1,newColor, value, used);
    }
}

题目二:岛屿数量(200. 岛屿数量

代码如下:

class Solution {
     public int numIslands(char[][] grid) {
        if (null == grid || 0 == grid.length) {
            return 0;
        }
        int sum = 0;
        for (int i = 0; i < grid.length; i ++) {
            for (int j = 0; j < grid[0].length; j ++) {
                if (grid[i][j] == '1') {
                    sum ++;
                    dfs(grid, i, j);
                }
            }
        }
        return sum;
    }
    private void dfs(char[][] grid, int i, int j) {
        if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0') {
            return;
        }
        grid[i][j] = '0';
        dfs(grid, i - 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i + 1, j);
        dfs(grid, i, j - 1);
    }
}

题目三: 被围绕的区域(130. 被围绕的区域)

题解:首先先从边界上开始查找是否有0的位置,将边界上0位置上所关联到的0都设置为Y,其次遍历这个区域,全部将0设置为x,最后将Y设置为0.
代码如下:

class Solution {
    public void solve(char[][] board) {
        if (null == board || 0 == board.length) {
            return;
        }
        for (int j = 0; j < board[0].length; j ++) {
            dfs(board, 0, j);
            dfs(board, board.length - 1, j);
        }
        for (int i = 1; i < board.length - 1; i ++) {
            dfs(board, i, 0);
            dfs(board, i, board[0].length - 1);
        }

        for (int i = 0; i < board.length; i ++) {
            for (int j = 0; j < board[0].length; j ++) {
                if (board[i][j] == 'O') {
                    board[i][j] = 'X';
                }
            }
        }

        for (int i = 0; i < board.length; i ++) {
            for (int j = 0; j < board[0].length; j ++) {
                if (board[i][j] == 'Y') {
                    board[i][j] = 'O';
                }
            }
        }

    }
    private void dfs(char[][] board, int i, int j) {
        if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] == 'X' || board[i][j] == 'Y') {
            return;
        }
        board[i][j] = 'Y';
        dfs(board, i - 1, j);
        dfs(board, i, j + 1);
        dfs(board, i + 1, j);
        dfs(board, i, j - 1);
    }
}

题目四:单词搜索(79. 单词搜索

代码如下:

class Solution {
    public boolean exist(char[][] board, String word) {
        if (null == board) {
            return false;
        }
        if (null == word || 0 == word.length()) {
            return true;
        }
        int[][] state = new int[board.length][board[0].length];
        for (int i = 0; i < board.length; i ++) {
            for (int j = 0; j < board[0].length; j ++) {
                if (dfs(i, j, 0, board, word, state)) {
                    return true;
                }
            }
        }
        return false;
    }
    private boolean dfs(int i, int j, int index, char[][] board, String word, int[][] state) {
         if (index == word.length()) {
            return true;
        }
        if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || state[i][j] == 1) {
            return false;
        }
        state[i][j] = 1;
        if (board[i][j] == word.charAt(index)) {
            boolean res = dfs(i - 1, j, index + 1, board, word, state) ||
                dfs(i + 1, j, index + 1, board, word, state) ||
                dfs(i, j - 1, index + 1, board, word, state) ||
                dfs(i, j + 1, index + 1, board, word, state);
            state[i][j] = 0;
            return res;    
        }
        state[i][j] = 0;
        return false;
    }
}

题型三:字符串中的回溯问题

字符串的问题的特殊之处在于,字符串的拼接生成新对象,因此在这一类问题上没有显示「回溯」的过程,但是如果使用 StringBuilder 拼接字符串就另当别论。

题目一:电话号码的字母组合(17. 电话号码的字母组合

代码如下:

class Solution {
    public List<String> letterCombinations(String digits) {
        if (null == digits || 0 == digits.length()) {
            return new ArrayList<>();
        }
        String[][] cs = {{}, {}, {"a","b","c"},{"d","e","f"},{"g","h","i"},{"j","k","l"},
        {"m","n","o"},{"p","q","r","s"},{"t","u","v"},{"w","x","y","z"}};
        List<String> res = new ArrayList<>();
        String str = new String();
        dfs(0,cs, digits,str,res);
        return res;
    }
    private void dfs(int step, String[][] cs, String digits, String str, List<String> res) {
        // step = 0
        if (step == digits.length() - 1) {
            if (digits.charAt(step) == '7' || digits.charAt(step) == '9') {
                for (int i = 0; i < 4; i ++) {
                    String s1 = str + cs[digits.charAt(step) - '0'][i];
                    res.add(s1);
                }
            } else {
                for (int i = 0; i < 3; i ++) {
                    String s1 = str + cs[digits.charAt(step) - '0'][i];
                    res.add(s1);
                }
            }
            return;
        }

        if (digits.charAt(step) == '7' || digits.charAt(step) == '9') {
            for (int i = 0; i < 4; i ++) {
                str += cs[digits.charAt(step) - '0'][i];
                dfs(step + 1, cs,digits, str,res);
                str = str.substring(0,str.length() - 1);
            }
        } else {
           for (int i = 0; i < 3; i ++) {
                str += cs[digits.charAt(step) - '0'][i];
                dfs(step + 1, cs,digits, str,res);
                str = str.substring(0,str.length() - 1);
            } 
        }
        return;
    }
}

题目二:字母大小写全排列(784. 字母大小写全排列

代码如下:

class Solution {
    public List<String> letterCasePermutation(String S) {
        int len = S.length();
        List<String> res = new ArrayList<>();
        if (0 == len) {
            return res;
        }
        char[] charArray = new char[len];
        dfs(S, len, 0, charArray, res);
        return res;
    }
    // start 从0开始
    private void dfs(String S, int len, int start, char[] charArray, List<String> res) {
        if (start == len) {
            res.add(new String(charArray));
            return;
        }
        charArray[start] = S.charAt(start);
        dfs(S, len, start + 1, charArray, res);
        if (Character.isLetter(S.charAt(start))) {
            // 大小写转化
            charArray[start] = (char)(S.charAt(start) ^ (1 << 5));
            dfs(S, len, start + 1, charArray, res);
        }
    }
}

题目三:括号生成(22. 括号生成

代码如下:

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        dfs("", n, n, res);
        return res;
    }
    private void dfs(String curStr, int left, int right, List<String> res) {
        if (0 == left && 0 == right) {
            res.add(curStr);
            return;
        }
        if (left > right) {
            return;
        }
        if (right > 0) {
            dfs(curStr + ")", left, right - 1, res);
        }
        if (left > 0) {
            dfs(curStr + "(", left - 1, right, res);
        }
    }
}

题型四:游戏问题

题目一:N 皇后(51. N 皇后)

题解:参考资料
代码如下:

class Solution {
    private int n;
    private boolean[] col;
    private boolean[] master;
    private boolean[] slave;
    private List<List<String>> res;
    public List<List<String>> solveNQueens(int n) {
        this.n = n;
        col = new boolean[n];
        master = new boolean[2 * n - 1];
        slave = new boolean[2 * n - 1];
        res = new ArrayList<>();
        Deque<Integer> stack = new ArrayDeque<>();
        dfs(0, stack);
        return res;
    }
    private void dfs(int row, Deque<Integer> stack) {
        if (row == n) {
            List<String> board = covert2Board(stack, n);
            res.add(board);
            return;
        }
        for (int i = 0; i < n; i ++) {
            if (!col[i] && !master[row + i] && !slave[row - i + n - 1]) {
                stack.addLast(i);
                col[i] = true;
                master[row + i] = true;
                slave[row - i + n - 1] = true;
                dfs(row + 1, stack);
                col[i] = false;
                master[row + i] = false;
                slave[row - i + n - 1] = false;
                stack.removeLast();
            }
        }
    }
    // 保存一个结果的stack,皇后数量:n
    private List<String> covert2Board(Deque<Integer> stack, int n) {
        List<String> board = new ArrayList<>();
        for (Integer num : stack) {
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < n; i ++) {
                sb.append(".");
            }
            sb.replace(num, num + 1, "Q");
            board.add(sb.toString());
        }
        return board;
    }
}

题目二:解数独(37. 解数独)

题解:参考资料
代码如下:

// 方块,行,列都是从0开始
class Solution {
    private boolean[][] rows;
    private boolean[][] cols;
    private boolean[][] blocks;
    public void solveSudoku(char[][] board) {
        rows = new boolean[9][10];
        cols = new boolean[9][10];
        blocks = new boolean[9][10];
        for (int i = 0; i < board.length; i ++) {
            for (int j = 0; j < board[0].length; j ++) {
                if (board[i][j] != '.') {
                    rows[i][board[i][j] - '0'] = true;
                    cols[j][board[i][j] - '0'] = true;
                    blocks[blockIndex(i, j)][board[i][j] - '0'] = true;
                }
            }
        }
        dfs(0, board);
    }
    // 题目要求只要找到一种解法即可,找到后就返回,而且还要保留这一种结果。
    // 找到后通过返回true即可向上退出。如果题目要求返回所有的结果即dfs方法返回void
    private boolean dfs(int depth, char[][] board) {
        if (depth == 81) {
            return true;
        }
        int row = rowIndex(depth);
        int col = colIndex(depth);
        if (board[row][col] != '.') {
            return dfs(depth + 1, board);
        } else {
            for (int i = 1; i < 10; i ++) {
                if (rows[row][i] || cols[col][i] || blocks[blockIndex(row, col)][i]) {
                    continue;
                }
                board[row][col] = (char)('0' + i);
                rows[row][i] = true;
                cols[col][i] = true;
                blocks[blockIndex(row, col)][i] = true;
                if (dfs(depth + 1, board)) {
                    return true;
                }
                board[row][col] = '.';
                rows[row][i] = false;
                cols[col][i] = false;
                blocks[blockIndex(row, col)][i] = false;
            }
        }
        return false;
    }
    private int blockIndex(int i, int j) {
        return i / 3 * 3 + j / 3;
    }
    private int rowIndex(int depth) {
        return depth / 9;
    }
    private int colIndex(int depth) {
        return depth % 9;
    }
   
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值