回溯法数字全排列_【LeetCode】回溯法总结

排列组合

排列

46. 全排列

给定一个没有重复数字的序列,返回其所有可能的全排列。

示例:

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

使用一个boolean数组标记当前递归链中已经访问过的元素,递归返回时,再将该元素标记为未访问。

在递归过程中,只需要保证在同一递归链中不要访问访问过的元素,但是可以访问已经访问过,但是不在当前递归链中的元素。

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

/**
 * 回溯法
 * @param curList 当前正在处理的字符串
 * @param permutations 排列组合结果集合
 * @param visited 标记当前递归链已经访问过的元素
 * @param nums 给定数组
 */
private void backtrack (List<Integer> curList,
                        List<List<Integer>> permutations,
                        boolean[] visited,
                        int[] nums) {
    if (curList.size() == nums.length) {
        permutations.add(new ArrayList<>(curList));
    } else {
        for (int i = 0; i < nums.length; i++) {
            if (visited[i]) {
                continue;
            }
            visited[i] = true;
            System.out.println(i+" before " + visited[i]);
            curList.add(nums[i]);
            backtrack(curList,permutations,visited,nums);
            curList.remove(curList.size() - 1);
            visited[i] = false;
            System.out.println(i+" after " + visited[i]);

        }
    }
}

47. 全排列 II(含有相同元素求排列)

给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

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

这一题跟 46. 全排列思路基本一致,不同的是重复元素的处理:

  • 给定数组先排序处理
  • 递归中,给curList添加元素时,看当前元素是否等于前一元素,如果相等且前一元素还没有被访问过,则跳过当前元素
public List<List<Integer>> permuteUnique (int[] nums) {
    List<List<Integer>> ans = new ArrayList<>();
    ArrayList<Integer> curList = new ArrayList<>();
    boolean[] visited = new boolean[nums.length];
    Arrays.sort(nums);
    backtrack(curList, ans, visited, nums);

    return ans;
}

/**
 * 回溯法
 * @param curList 当前正在处理的字符串
 * @param permutations 排列组合结果集合
 * @param visited 标记当前递归链已经访问过的元素
 * @param nums 给定数组
 */
private void backtrack (List<Integer> curList,
                        List<List<Integer>> permutations,
                        boolean[] visited,
                        int[] nums) {
    if (curList.size() == nums.length) {
        permutations.add(new ArrayList<>(curList));
    } else {
        for (int i = 0; i < nums.length; i++) {
            if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]){
                continue;
            }
            if (visited[i]) {
                continue;
            }
            visited[i] = true;
            curList.add(nums[i]);
            backtrack(curList, permutations, visited, nums);
            curList.remove(curList.size() - 1);
            visited[i] = false;
        }
    }
}

组合

77. 组合

给定两个整数 nk,返回 1 ... n 中所有可能的 k 个数的组合。

示例:

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

与排列不同的是,本题递归是做减法的,当 k 递减到 0 的时候,将当前递归链的集合加入到结果集合。

public List<List<Integer>> combine (int n, int k) {
    List<List<Integer>> ans = new ArrayList<>();
    List<Integer> curList = new ArrayList<>();
    backtrack(curList, ans, 1, n, k);
    return ans;
}

private void backtrack (List<Integer> curList,
                        List<List<Integer>> combinations,
                        int start,
                        int n,
                        int k) {
    if (k == 0) {
        combinations.add(new ArrayList<>(curList));
    } else {
        for (int i = start; i <= n - k + 1; i++) {
            curList.add(i);
            backtrack(curList, combinations, i + 1, n, k-1);
            curList.remove(curList.size() - 1);
        }
    }
}

39. 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]

示例 2:

输入: candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

因为一个组合中允许有同样的元素,所以每次递归中改变的只有target的值,每次减小,减掉当前访问的数组元素,直到target == 0说明集合凑好了,加入结果集合。

public List<List<Integer>> combinationSum (int[] candidates, int target) {
    List<List<Integer>> ans = new ArrayList<>();
    List<Integer> curList = new ArrayList<>();
    backtrack(curList, ans, 0, candidates, target);
    return ans;
}

private void backtrack (List<Integer> curList,
                        List<List<Integer>> combinationSum,
                        int start,
                        int[] candidates,
                        int target) {
    if (target == 0) {
        combinationSum.add(new ArrayList<>(curList));
    } else {
        for (int i = start; i < candidates.length; i++) {
            if (candidates[i] <= target) {
                curList.add(candidates[i]);
                backtrack(curList,
                        combinationSum,
                        i,
                        candidates,
                        target - candidates[i]);
                curList.remove(curList.size() - 1);
            }
        }

    }
}

40. 组合总和 II(含有相同元素的组合求和)

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

  • 所有数字(包括目标数)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]

因为给定数组中的每个元素只能使用一次,参考47. 全排列 II中的做法先做排序处理,并且设置一个数组来标记每个元素是否被访问,递归中,给curList添加元素时,看当前元素是否等于前一元素,如果相等且前一元素还没有被访问过,则跳过当前元素。

public List<List<Integer>> combinationSum2 (int[] candidates, int target) {
    List<List<Integer>> ans = new ArrayList<>();
    List<Integer> curList = new ArrayList<>();
    boolean[] visited = new boolean[candidates.length];
    Arrays.sort(candidates);
    backtrack(ans, curList, visited, 0, candidates, target);
    return ans;
}

private void backtrack (List<List<Integer>> combinationSum,
                        List<Integer> curList,
                        boolean[] visited,
                        int start,
                        int[] candidates,
                        int target) {
    if (target == 0) {
        combinationSum.add(new ArrayList<>(curList));
    } else {
        for (int i = start; i < candidates.length; i++) {
            if (i != 0 && candidates[i] == candidates[i - 1] && !visited[i - 1]) {
                continue;
            }
            if (candidates[i] <= target) {
                visited[i] = true;
                curList.add(candidates[i]);
                backtrack(combinationSum,
                        curList,
                        visited,
                        i + 1,
                        candidates,
                        target - candidates[i]);
                curList.remove(curList.size() - 1);
                visited[i] = false;
            }
        }
    }
}

216. 组合总和 III(1-9 数字的组合求和)

找出所有相加之和为 nk 个数的组合组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:

  • 所有数字都是正整数。
  • 解集不能包含重复的组合。

示例 1:

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

示例 2:

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

  • 可以看做在一个给定数组是{1,2,...,9}找 k 个数 ,使它们和为 n 。
  • 给定数组不含有重复元素,所以不需要像40. 组合总和 II(含有相同元素的组合求和)中这样判重。
  • 要同时满足k == 0n == 0才能把当前的列表加入到结果集合。
public List<List<Integer>> combinationSum3 (int k, int n) {
        List<List<Integer>> ans = new ArrayList<>();
        backtrack(ans, new ArrayList<>(), 1, k, n);
        return ans;
    }

    private void backtrack (List<List<Integer>> combinationSum,
                            List<Integer> curList,
                            int start,
                            int k,
                            int n) {
        if (k == 0 && n == 0) {
            combinationSum.add(new ArrayList<>(curList));
        } else {
            for (int i = start; i <= 9; i++) {

                if (i <= n) {
                    curList.add(i);
                    backtrack(combinationSum, curList, i + 1, k - 1, n-i);
                    curList.remove(curList.size() - 1);
                }
            }
        }
    }

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

9fe5aa0eb7194a002055faced2a2c532.png

示例:

输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

说明: 尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。


首先用一个数组把每个数字按键对应的字母存储起来,再把给定的字符串digits拆分成每一个单独的数字做处理。使用一个辅助函数进行处理

private void helper (String digits, int i,  StringBuilder cur, List<String> ans);
/*
String digits : 给定的数字组合字符串
int i :当前(递归)处理的层数
StringBuilder cur :当前字母组合
List<String> ans :结果列表
*/

处理过程如下:

  • 取出当前数字,以及该数字按键对应的字母,存为一个char数组curLetters
  • curLetters进行遍历,将当前字符加入当前字母组合cur,然后(递归地)处理后一个数字
  • 当层数等于给定字符串digits长度时,将当前字母组合cur加入结果列表ans,删除刚用过的字符(也就是当前字母组合的最后一个字母)
  • 遍历完成后,结果列表ans就是题目要求的结果
private final static String[] digitsToChars =
            {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

    public List<String> letterCombinations (String digits) {
        List<String> ans = new ArrayList<>();
        if (digits == null || digits.length() == 0) {
            return ans;
        }
        doCombination(new StringBuilder(), ans, digits);
        return ans;
    }

    private void doCombination (StringBuilder prefix, List<String> combination, String digits) {
        if (prefix.length() == digits.length()) {
            combination.add(prefix.toString());
        } else {
            int curDiggit = digits.charAt(prefix.length()) - '0';
            for (char c : digitsToChars[curDiggit].toCharArray()) {
                prefix.append(c);
                doCombination(prefix,combination,digits);
                prefix.deleteCharAt(prefix.length() - 1);
            }
        }
    }

路径

79. 单词搜索

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例:

board =
[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]

给定 word = "ABCCED", 返回 true.
给定 word = "SEE", 返回 true.
给定 word = "ABCB", 返回 false.

该回溯算法的辅助方法流程如下:

  • 如果当前字符串索引index == word.length(),表示有word.length()次匹配都成功了,直接返回true
  • 如果当前二维面板的索引超出有意义的范围或当前考察的二维面板的值当前字符串的值不匹配,则返回false,结束这次搜索
  • 将当前二维面板的值取出来暂存,为后面回溯做准备
  • 用一个符号*表示这个元素已经使用过了,下次搜索就不能考虑它了(满足题目不能重复的要求)
  • (递归地)搜索当前元素周围的元素,看能否继续匹配,直到得到一个结果
  • 将当前元素从*还原为以前的字符

最后,遍历整个二维面板,对每一个匹配到word.charAt(0)的元素进行一次搜索,返回结果。

private int m, n;

public boolean exist (char[][] board, String word) {
    if (board == null || board.length == 0) {
        return false;
    }
    m = board.length;
    n = board[0].length;

    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (board[i][j] == word.charAt(0)) {
                return backtrack(i, j, 0, board, word);
            }
        }
    }
    return false;
}

private boolean backtrack (int i, int j, int index, char[][] board, String word) {
    if (index == word.length()) {
        return true;
    }
    if (i < 0 || i >= m || j < 0 || j >= n ||
            board[i][j] != word.charAt(index)) {
        return false;
    }
    char temp = board[i][j];
    board[i][j] = '*';
    boolean ans;
    ans = backtrack(i + 1, j, index + 1, board, word) ||
            backtrack(i - 1, j, index + 1, board, word) ||
            backtrack(i, j + 1, index + 1, board, word) ||
            backtrack(i, j - 1, index + 1, board, word);
    board[i][j] = temp;
    return ans;
}

257. 二叉树的所有路径

给定一个二叉树,返回所有从根节点到叶子节点的路径。

说明: 叶子节点是指没有子节点的节点。

示例:

输入:

   1
 /   
2     3
 
  5

输出: ["1->2->5", "1->3"]

解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3

可以当做二叉树的遍历来处理,当碰到叶子节点的时候将当前路径加入到结果集合。

List<String> ans = new ArrayList<>();

public List<String> binaryTreePaths (TreeNode root) {
    if (root == null)
        return ans;
    String cur = "";
    findPaths(root,cur);
    return ans;
}

private void findPaths (TreeNode node, String curStr) {
    if (node.right == null && node.left == null) {
        curStr += node.val;
        ans.add(curStr);
        return;
    }
    curStr += node.val + "->";
    if (node.left != null)
        findPaths(node.left, curStr);
    if (node.right != null)
        findPaths(node.right, curStr);
}

子集

78. 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

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

一个集合的子集按长度可以分为0~nums.length种,可以分 n 次循环,找到不同长度的子集。

每次大的递归中:

  • 长度达到了,就加入结果集合
  • 长度没达到,就在数组剩下的元素里面继续回溯递归
  • 因为给定数组不含重复元素,所以不考虑判重
public List<List<Integer>> subsets (int[] nums) {
    List<List<Integer>> ans = new ArrayList<>();
    for (int size = 0; size <= nums.length; size++) {
        backtrack(0,size,new ArrayList<>(),ans,nums);
    }
    return ans;
}

private void backtrack (int start,int size, ArrayList<Integer> curList, List<List<Integer>> subsets,int[] nums) {
    if (curList.size() == size) {
        subsets.add(new ArrayList<>(curList));
    } else {
        for (int i = start; i < nums.length; i++) {
            curList.add(nums[i]);
            backtrack(i + 1, size, curList, subsets, nums);
            curList.remove(curList.size() - 1);
        }
    }
}

90. 子集 II(含有相同元素求子集)

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

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

与78. 子集不同的是判重的处理。

判重方法与之前类似

  • 数组先排序处理
  • 设置标记数组,标记是否访问过,如果当前元素与前一个元素相等且前一元素没有处理过,则跳过当前元素
public List<List<Integer>> subsetsWithDup (int[] nums) {
    List<List<Integer>> ans = new ArrayList<>();
    boolean[] visited = new boolean[nums.length];
    Arrays.sort(nums);
    for (int size = 0; size <= nums.length; size++) {
        backtrack(ans,new ArrayList<>(),visited,0,size,nums);
    }
    return ans;
}

private void backtrack (List<List<Integer>> subsets,
                        List<Integer> curList,
                        boolean[] visited,
                        int start,
                        int size,
                        int[] nums) {
    if (curList.size() == size) {
        subsets.add(new ArrayList<>(curList));
    } else {
        for (int i = start; i < nums.length; i++) {
            if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
                continue;
            }

            visited[i] = true;
            curList.add(nums[i]);
            backtrack(subsets, curList, visited, i + 1, size, nums);
            curList.remove(curList.size() - 1);
            visited[i] = false;
        }
    }
}

131. 分割回文串

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

示例:

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

首先需要一个判断当前字符串是不是回文串的方法:

private boolean isPalindrome (String s, int end) {
    int start = 0;
    while (start < end) {
        if (s.charAt(start++) != s.charAt(end--)) {
            return false;
        }
    }
    return true;
}

本题的回溯处理是不断切分剩下的字符串,判断是否为回文串,是回文串就加入暂存列表,一直到把字符串分割完毕,就把暂存列表加入结果集合。

public List<List<String>> partition(String s) {
    List<List<String>> ans = new ArrayList<>();
    backtrack(ans, new ArrayList<>(), s);
    return ans;
}

private void backtrack (List<List<String>> partitions,
                        List<String> curList,
                        String s) {
    if (s.length() == 0) {
        partitions.add(new ArrayList<>(curList));
    } else {
        for (int i = 0; i < s.length(); i++) {
            if (isPalindrome(s, i)) {
                curList.add(s.substring(0, i + 1));
                backtrack(partitions, curList, s.substring(i + 1));
                curList.remove(curList.size() - 1);
            }
        }
    }
}

private boolean isPalindrome (String s, int end) {
    int start = 0;
    while (start < end) {
        if (s.charAt(start++) != s.charAt(end--)) {
            return false;
        }
    }
    return true;
}

93. 复原IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

示例:

输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]

IP地址由32位二进制数组成,为便于使用,常以XXX.XXX.XXX.XXX形式表现,每组XXX代表小于或等于255的10进制数。例如维基媒体的一个IP地址是208.80.152.2。地址可分为A、B、C、D、E五大类,其中 E类属于特殊保留地址。
——维基百科

变量之间的关系:

  • List<String> addresses
  • StringBuilder tempAdd
    • String part

从下往上看

  • part 的值小于 255 才是 IP 地址的一段,才能被加入临时 IP 地址 tempAdd
  • 递归进行到第 4 层 且 剩余的字符串长度为 0 的时候,tempAdd才能被加入结果集合addresses

其他几个要点:

  • 某一段如果大于 1 位,不能以 0 作为开头
  • 不是第一段的part就在前面加个 "."
public List<String> restoreIpAddresses (String s) {
    List<String> ans = new ArrayList<>();
    doRestore(0,new StringBuilder(),ans,s);
    return ans;
}

/**
 *
 * @param k IP 地址的第 k 段
 * @param tempAdd IP 地址的第 k 段内容
 * @param addresses IP 地址结果集合
 * @param s 给定字符串的待处理部分
 */
private void doRestore (int k, 
                        StringBuilder tempAdd, 
                        List<String> addresses,
                        String s) {
    if (k == 4 || s.length() == 0) {
        if (k == 4 && s.length() == 0) {
            addresses.add(tempAdd.toString());
        }
    } else {
        for (int i = 0; i < s.length() && i <= 2; i++) {
            if (i != 0 && s.charAt(0) == '0') {
                // 某一段如果大于 1 位,不能以 0 作为开头
                break;
            }
            String part = s.substring(0,i+1);
            if (Integer.valueOf(part) <= 255) {
                if (tempAdd.length() != 0) {  
                    // 不是第一部分就在前面加个 "."
                    part = "." + part;
                }
                tempAdd.append(part);
                doRestore(k+1,tempAdd,addresses,s.substring(i+1));
                tempAdd.delete(tempAdd.length() - part.length(), tempAdd.length());
            }

        }
    }
}

棋盘

37. 解数独

编写一个程序,通过已填充的空格来解决数独问题。

一个数独的解法需遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

空白格用 '.' 表示。

1afb1d9958471a390de0e5082503a066.png

一个数独。

5a3cbbade9a8e6b83bb10821120551fd.png

答案被标成红色。

Note:

  • 给定的数独序列只包含数字 1-9 和字符 '.'
  • 你可以假设给定的数独只有唯一解。
  • 给定数独永远是 9x9 形式的。

  1. 构造数组,标记同一行,同一列,同一大格中是否含有某个元素
private boolean[][] rowUsed = new boolean[9][10];
private boolean[][] colUsed = new boolean[9][10];
private boolean[][] cubeUsed = new boolean[9][10];

比如,rowUsed[i][num]就表示第i行是否有 num 这个数。其中cubeUsed[][]的第一个索引需要做一个映射,表示当前元素在第几个大格中:

private int cubeNum (int row, int col) {
    int r = row / 3;
    int c = col / 3;
    return r * 3 + c;
}
  1. 然后,使用双层for循环遍历棋盘,目的是初始化棋盘,也就是初始化第一步中的三个数组,将棋盘上已有的数字做好标记。
  2. 最后开始回溯法从左到右,从上到下遍历棋盘,碰到是数字的小格才处理,尝试将1~9的数字填进去,看是否满足条件(行、列、大格)
  3. 如果满足,就做好标记(行、列、大格),棋盘上填入数字,继续回溯递归查看能否满足全局条件,如果不满足数独全局条件,就将当前填入的数字删掉,标记数组抹去(回溯)
  4. 如果不满足,换个数试试
private boolean[][] rowUsed = new boolean[9][10];
private boolean[][] colUsed = new boolean[9][10];
private boolean[][] cubeUsed = new boolean[9][10];
private char[][] board = new char[9][9];

public void solveSudoku (char[][] board) {
    this.board = board;
    for (int i = 0; i < board.length; i++) {
        for (int j = 0; j < board[0].length; j++) {
            if (board[i][j] == '.') {
                continue;
            }
            int num = board[i][j] - '0';
            rowUsed[i][num] = true;
            colUsed[j][num] = true;
            cubeUsed[cubeNum(i, j)][num] = true;
        }
    }
    backtrack(0, 0);
}

private boolean backtrack (int row, int col) {
    while (row < 9 && board[row][col] != '.') {
        row = col == 8 ? row + 1: row;
        col = col == 8 ? 0 : col + 1;
    }
    if (row == 9) {
        return true;
    }
    for (int num = 1; num <= 9; num++) {
        if (rowUsed[row][num] || colUsed[col][num] || cubeUsed[cubeNum(row, col)][num]) {
            continue;
        }
        rowUsed[row][num] = colUsed[col][num] = cubeUsed[cubeNum(row, col)][num] = true;
        board[row][col] = (char) (num + '0');
        if (backtrack(row, col)) {
            return true;
        }
        board[row][col] = '.';
        rowUsed[row][num] = colUsed[col][num] = cubeUsed[cubeNum(row, col)][num] = false;
    }
    return false;
}


private int cubeNum (int row, int col) {
    int r = row / 3;
    int c = col / 3;
    return r * 3 + c;
}

51. N皇后

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

375efd18ed3f40cfeaaa6396b4fdfc89.png

上图为 8 皇后问题的一种解法。

给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q''.' 分别代表了皇后和空位。

示例:

输入: 4
输出: [
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。

  1. 因为我们按行进行递归回溯,所以同一行中不可能出现两个皇后,剩下的(列、45度对角线、135度对角线)我们需要三个数组来标记,长度分别为 n, 2n-1, 2n -1
private boolean[] colUsed;
private boolean[] diagonals45Used;
private boolean[] diagonals135Used;
  1. 按行进行递归回溯,如果遍历到了最后一行了,说明前面的都成立了,我们就把当前棋盘加入结果集合,否则就按正常回溯操作继续递归
  2. 45度和135度对角线是否有皇后的标记数组需要做一个索引映射,假设一个元素的坐标为(row, col)则45度和135度对角线标记数组对应的索引分别为row + coln-1-(row-col)

10fb5cce173624b22e1dc72771ec8a10.png
private List<List<String>> solutions;
private char[][] nQueens;
private int n;
private boolean[] colUsed;
private boolean[] diagonals45Used;
private boolean[] diagonals135Used;

public List<List<String>> solveNQueens (int n) {
    solutions = new ArrayList<>();
    nQueens = new char[n][n];
    for (char[] nQueen : nQueens) {
        Arrays.fill(nQueen, '.');
    }
    colUsed = new boolean[n];
    diagonals45Used = new boolean[2 * n - 1];
    diagonals135Used = new boolean[2 * n - 1];
    this.n = n;
    backtrack(0);
    return solutions;
}

private void backtrack (int row) {
    if (row == n) {
        List<String> list = new ArrayList<>();
        for (char[] nQueen : nQueens) {
            list.add(new String(nQueen));
        }
        solutions.add(list);
    } else {
        for (int col = 0; col < n; col++) {
            int diagonals45Index = row + col;
            int diagonals135Index = n - 1 - (row - col);
            if (colUsed[col]
                    || diagonals45Used[diagonals45Index]
                    || diagonals135Used[diagonals135Index]) {
                continue;
            }
            nQueens[row][col] = 'Q';
            colUsed[col]
                    = diagonals45Used[diagonals45Index]
                    = diagonals135Used[diagonals135Index]
                    = true;
            backtrack(row + 1);
            nQueens[row][col] = '.';
            colUsed[col]
                    = diagonals45Used[diagonals45Index]
                    = diagonals135Used[diagonals135Index]
                    = false;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值