LeetCode刷题笔记之回溯算法(二)

一、排列问题

排列是有序的,即{1,2}与{2,1}是两个排序。

1. 46【全排列】

  • 题目: 给定一个不含重复数字的数组 nums ,返回其所有可能的全排列。你可以按任意顺序返回答案。
  • 代码:
class Solution {
    List<List<Integer>> ansList = new ArrayList<>();
    List<Integer> path = new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
        //排列是有序的,因此每一层用过的元素,在下一层还可以使用
        //但是需要记录在同一递归循环中是否使用过该元素
        //终止条件是path中的元素个数==nums.length
        boolean[] used = new boolean[nums.length];
        backtrack(nums,used);
        return ansList;
    }
    public void backtrack(int[] nums,boolean[] used){
        if(path.size() == nums.length) {
            ansList.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if(used[i]) continue;
            path.add(nums[i]);
            used[i] = true;
            backtrack(nums,used);
            path.removeLast();
            used[i] = false;
        }
    }
}

2. 47【全排列 II】

  • 题目: 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
  • 代码:
class Solution {
    public List<List<Integer>> ansList = new ArrayList<>();
    public List<Integer> path = new LinkedList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        //本题与上一题的区别是本题含有重复数据
        //因此在每一递归层需要判断前面是否出现过相同元素
        //此外输入数组不一定是有序的,因此需要对数组进行排序
        Arrays.sort(nums);
        boolean[] used = new boolean[nums.length];
        backtrack(nums,used);
        return ansList;
    }
    public void backtrack(int[] nums,boolean[] used){
        if(path.size() == nums.length){
            ansList.add(new ArrayList<>(path));
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            if(i>0 && nums[i]==nums[i-1]) {
                //如果上一个与当前元素相等的数被使用过,则代表两个数位于同一递归分支
                //我们需要对同一递归层中相同的元素进行剪枝,也就是说上一个元素不能被使用
                if(!used[i-1]) continue;
            }
            if(!used[i]){
                path.add(nums[i]);
                used[i] = true;
                backtrack(nums,used);
                path.removeLast();
                used[i] = false;
            }
        }
    }
}

二、棋盘问题

1. 51【N皇后】

  • 题目: 按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
  • 代码:
class Solution {
    List<List<String>> ansList = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
        //N皇后问题:皇后的位置不能同行,同列,对角
        //棋盘是个n×n的数组,因此可以将第i行看成树的第i层
        //第j列看成树的第j个分支
        char[][] chessboard = new char[n][n];
        for(char[] cArr: chessboard){
            Arrays.fill(cArr,'.');
        }
        backtrack(n,chessboard,0);
        return ansList;
    }
    public void backtrack(int n,char[][] chessboard,int row){
        if(row == n){
            List<String> sList = new ArrayList<>();
            for (char[] cArr:chessboard) {
                sList.add(String.valueOf(cArr));
            }
            ansList.add(sList);
        }
        for (int col = 0; col <n ; col++) {
            if(isValid(n,chessboard,row,col)){
                chessboard[row][col] = 'Q';
                backtrack(n,chessboard,row+1);
                chessboard[row][col] ='.';
            }
        }
    }
    public boolean isValid(int n,char[][] chessboard,int row,int col){
        //因为一行只能有1个皇后,所以不需要检查第row行是否合法
        for (int i = 0; i < row; i++) {
            if(chessboard[i][col] == 'Q'){
                return false;
            }
        }
        //检查左上角
        for (int i=row-1,j=col-1; i>=0 && j>=0; i--,j--) {
            if(chessboard[i][j] == 'Q'){
                return false;
            }
        }
        //检查右上角
        for (int i=row-1,j=col+1; i>=0 && j<n; i--,j++) {
            if(chessboard[i][j] == 'Q'){
                return false;
            }
        }
        return true;
    }
}

2. 52【N皇后Ⅱ】

  • 题目: n皇后问题 研究的是如何将 n 个皇后放置在 n × n 的棋盘上,并且使皇后彼此之间不能相互攻击。
    给你一个整数 n ,返回 n 皇后问题 不同的解决方案的数量。
  • 代码:
class Solution {
    int num = 0;
    public int totalNQueens(int n) {
        char[][] board = new char[n][n];
        for(char[] c:board){
            Arrays.fill(c,'.');
        }
        backtrack(n,board,0);
        return num;
    }
    public void backtrack(int n,char[][] board,int row){
        if(row == n){
            num++;
            return;
        }
        for (int col = 0; col < n; col++) {
            if(isValid(row,col,board,n)){
                board[row][col] = 'Q';
                backtrack(n,board,row+1);
                board[row][col] = '.';
            }
        }
    }
    public boolean isValid(int row,int col,char[][] board,int n){
        for (int i = 0; i < row; i++) {
            if(board[i][col] == 'Q'){
                return false;
            }
        }
        for(int i=row-1,j=col-1;i>=0 && j>=0;i--,j--){
            if(board[i][j] == 'Q'){
                return false;
            }
        }
        for(int i=row-1,j=col+1;i>=0 && j<n;i--,j++){
            if(board[i][j] == 'Q'){
                return false;
            }
        }
        return true;
    }
}

3. 37【解数独】

  • 题目: 编写一个程序,通过填充空格来解决数独问题。数独部分空格内已填入了数字,空白格用 ‘.’ 表示。
    数独的解法需 遵循如下规则:
    • 数字 1-9 在每一行只能出现一次。
    • 数字 1-9 在每一列只能出现一次。
    • 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
  • 代码:
class Solution {
    public void solveSudoku(char[][] board) {
        //数独问题与N皇后问题不同,N皇后每次递归对行进行一次操作
        //而数独需要多次对同一行进行操作
        //所以使用两个for循环来进行递归
        //每次for循环找到符合条件的值就不在继续进行,因此回溯需要有boolean返回值
        backtrack(board);
    }
    public boolean backtrack(char[][] board){
        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++) {
                    if(isValid(i,j,k,board)){
                        board[i][j] = k;
                        if(backtrack(board)) return true;
                        board[i][j] = '.';
                    }
                }
                return false;
            }
        }
        return true;
    }
    public boolean isValid(int row,int col,char k,char[][] board){
        //判断行
        for (int i = 0; i < 9; i++) {
            if(board[i][col] == k){
                return false;
            }
        }
        //判断列
        for (int i = 0; i < 9; i++) {
            if(board[row][i] == k){
                return false;
            }
        }
        for (int i = (row/3)*3; i < (row/3)*3+3; i++) {
            for (int j = (col/3)*3; j < (col/3)*3+3; j++) {
                if(board[i][j] == k){
                    return false;
                }
            }
        }
        return true;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值