LeetCode 46、47、51 回溯(四)

91 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

本文包含以下几题:

  1. 给定一个不包含重复数字的数组nums,返回其所有可能的全排列。
  2. 给定一个包含重复数字的数组nums,返回其所有可能的全排列。
  3. N皇后问题。根据国际象棋的规则,皇后可以攻击同处于同一行或同一列或者同一对角线上的棋子,给定一个整数n,返回所有不同的n皇后解决方案。

解题思路

LeetCode 46 全排列

本题是通过回溯寻找二叉树上的所有叶子节点,和之前回溯区别在于本题不需要确定起始索引,但在回溯过程中需要判断当前数字是否已经被使用,如果被使用则跳过,回溯终止条件为临时集合的长度等于数组长度,可得代码如下:

private List<List<Integer>> resPer = new ArrayList<>();

public List<List<Integer>> permute(int[] nums) {
    boolean[] visited = new boolean[nums.length];
    backtraceP(nums, new ArrayList<>(), visited);
    return resPer;
}

public void backtraceP(int[] nums, ArrayList<Integer> temp, boolean[] visited){
    if(temp.size()==nums.length){
        resPer.add(new ArrayList<>(temp));
        return;
    }
    for(int i=0;i<nums.length;i++){
        if(visited[i]) continue;
        temp.add(nums[i]);
        visited[i] = true;
        backtraceP(nums, temp, visited);
        temp.remove(temp.size()-1);
        visited[i] = false;
    }
}

LeetCode 47 全排列II

本题是上题的变体,区别就是数组中包含相同的数字,那在上一题的基础上就需要做去重,去重的思路是二叉树的同层元素不能相同,因为不考虑输出顺序,因此可以使用之前的对数组排序+used数组的方法进行去重,另一个方法就是本题已经确定给定的数组范围是-10-10,因此可以使用长度为21的数组进行记录使用情况,下面的思路为第二种,代码如下:

private List<List<Integer>> resPermu = new ArrayList<>();

public List<List<Integer>> permuteUnique(int[] nums) {
    boolean[] visited = new boolean[nums.length];
    backtracePer(nums, new ArrayList<>(), visited);
    return resPermu;
}

public void backtracePer(int[] nums, ArrayList<Integer> temp, boolean[] visited){
    if(temp.size()==nums.length){
        resPermu.add(new ArrayList<>(temp));
        return;
    }
    int[] used = new int[21];
    for(int i=0;i<nums.length;i++){
        if(visited[i]||used[nums[i]+10]==1) continue;
        temp.add(nums[i]);
        used[nums[i]+10]=1;
        visited[i] = true;
        backtracePer(nums, temp, visited);
        temp.remove(temp.size()-1);
        visited[i] = false;
    }
}

LeetCode 51 N皇后

N皇后问题要求不能同行同列且同对角线,那如何使用回溯是一个较难的问题,不能同行同列就意味着每行每列都最多只存在一个元素,那想象一下回溯的多叉树,如果选定了第一行的某个元素位置,那下一个位置只能在第二行选择,同理如下。

即我们可以选择多叉树的高度为棋盘的高度,而同一层子树的选择则为某行的所有列,此时就可以进行回溯了。回溯重要的条件是找到回溯终止条件,此处很显然终止条件为高度超出棋盘行,而判断是否同行同列很简单,斜线我们除了需要判断45度的还需要判断135度的。

整体分析可得代码如下:

private List<List<String>> resSo = new ArrayList<>();

public List<List<String>> solveNQueens(int n) {
    char[][] c = new char[n][n];
    for (char[] c1 : c) {
        Arrays.fill(c1, '.');
    }
    backtrace(n, 0, c);
    return resSo;
}

public void backtrace(int n, int curRow, char[][] c){
    if(curRow == n){
        ArrayList<String> temp = new ArrayList<>();
        for (char[] c1 : c) {
            temp.add(String.valueOf(c1));
        }
        resSo.add(temp);
        return;
    }
    for(int i=0;i<n;i++){
        if(isProper(c, curRow, i, n)){
            c[curRow][i] = 'Q';
            backtrace(n, curRow+1, c);
            c[curRow][i] = '.';
        }
    }
}

public boolean isProper(char[][] c, int row, int column, int n){
    // 检查行
    for(int i=0;i<n;i++){
        if(c[i][column]=='Q') return false;
    }
    // 检查列
    for(int i=0;i<n;i++){
        if(c[row][i]=='Q') return false;
    }
    // 检查45度对角
    for(int i=row-1, j=column-1;i>=0&&j>=0;i--,j--){
        if(c[i][j] == 'Q') return false;
    }
    // 检查135度对角
    for(int i=row-1, j=column+1;i>=0&&j<n;i--,j++){
        if(c[i][j] == 'Q') return false;
    }
    return true;
}