递归算法还能够解决的一个典型问题,是具有树形结构的问题,当我们发现一个问题与一个更小的问题之间存在递推的关系时,递归关系呈现出来的是一个树形结构

树形问题:分析问题时,发现解决问题的思路本质是一棵树的形状

  • 回溯算法是在树形图上的深度优先遍历
  • 回溯的产生是由于深度优先遍历的回退,造成了状态重置或撤销选项

解决回溯问题的第一步是画出树形图

回溯的本质是DFS,回溯只是DFS产生的现象

  • 本质上是遍历的算法,全程使用一份状态变量去搜索状态空间里的所有状态
  • 具有DFS的自叶子到根的特点

递归与回溯 - 图1

EG:

17. 电话号码的字母组合

递归与回溯 - 图2

“23”可以分解为如下的树形

递归与回溯 - 图3

得到以下分析:

设,digits代表数字字符串,s(digits)是digits所能代表的字母字符串,,letter( digits[i] ) 表示第 i 个数字能表示的所有字母

则有:

  • s( digits[0…n-1] )

= letter( digits[0] ) + s( digits[1…n-1] )

= letter( digits[0] ) + letter( digits[1] ) + s( digits[2…n-1] )

= ……

思路步骤:

  1. 每次从digits中取出一个数字字符,找到其对应的字母字符串并循环

  2. 以当前的字母字符串为基础,从digits中取出下一个数字字符,找出字母字符串,递归取出所有数字字符并将当前字符与之前的字符串进行拼接 (s( digits[0…n-1] ) = letter( digits[0] ) + s( digits[1…n-1] ))

  3. 直到构成的解 s 长度与digits长度相同 (因为有多少数字组成的每一个字母字符串就有多少位,因此只要s的长度等于digits的长度),就说明完成了一个字母组合,存入list

  4. 递归上述步骤,直到所有第一个数字对应的字母字符串的所有字母组合完毕

  1. /**
  2. * @Author: antigenMHC
  3. * @Date: 2020/3/27 11:47
  4. * @Version: 1.0
  5. **/
  6. public class test {
  7. public static void main(String[] args) {
  8. List<String> strings = new Solution().letterCombinations("23");
  9. for (String s : strings) {
  10. System.out.println(s);
  11. }
  12. }
  13. }
  14. class Solution {
  15. private String[] letterTable = new String[]{
  16. "", //0
  17. "", //1
  18. "abc", //2
  19. "def", //3
  20. "ghi", //4
  21. "jkl", //5
  22. "mno", //6
  23. "pqrs", //7
  24. "tuv", //8
  25. "wxyz", //9
  26. };
  27. private List<String> lists = new LinkedList<>();
  28. //返回值为digits所能表示的所有字母字符串组成的list
  29. public List<String> letterCombinations(String digits) {
  30. find(digits, 0, "");
  31. return lists;
  32. }
  33. //index表示每次处理的数字在数字字符串中的位置
  34. //该函数每次处理一位数字!
  35. //s表示index之前已经转换了的数字,即 s ( digits[0, index-1] )
  36. //寻找和digits[index]匹配的字母字符串,获得 letter( digits[index] )
  37. //s ( digits[0, index-1] ) + letter( digits[index] ) = s ( digits[0, index] ) 即得到了一个新解
  38. private void find(String digits, int index, String s){
  39. //到达digits末尾,生成一个字母字符串
  40. if(s.length() == digits.length()) {
  41. lists.add(s);
  42. return;
  43. }
  44. char nowDigits = digits.charAt(index); //获取对应数字字符
  45. assert (nowDigits >= '2' && nowDigits <= '9');
  46. String letters = letterTable[nowDigits-'0']; //提取数字对应的字母字符串
  47. //遍历digits对应的字符串,每次得到一个字母,然后继续向下搜索,找出其后后面所有数字的组合
  48. for(int i=0; i<letters.length(); i++){
  49. //s+letters.charAt(i), 基于当前字母组合继续向下寻找
  50. //digits有多少数字,最终的每一个字母字符串就有多少位,因此只需要将数字对应的字母字符串中的每一个字母取出来,与前面的拼接,然后继续对下一个数字
  51. //进行字母字符的求解即可
  52. find(digits, index+1, s+letters.charAt(i));
  53. }
  54. return;
  55. }
  56. }

上述代码画出图来可以很好地理解

通过递归调用来寻找答案的过程也称为回溯,回溯法是暴利解法的一个主要实现手段

从树的形状来看和结果来看,结果的个数大致为 3 (n为树深度),所以时间复杂度为 O(2)

回溯算法能够处理的问题

1.排列问题

46. 全排列

递归与回溯 - 图4

排列 ( nums[0…n-1] ) = {取出一个数字} + 排列 ( nums[0…n-1 - 这个数字] )

排列问题中,元素之间是相互冲突的,因此需要判断和回溯(使用了一个元素并存入list中后,去掉这个元素,选择下一个元素)

  1. class Solution {
  2. private List<List<Integer>> reList = new ArrayList<>();
  3. //一个List<Integer>为一个排列
  4. public List<List<Integer>> permute(int[] nums) {
  5. if(nums.length==0) return reList;
  6. find(nums, 0, new ArrayList<>());
  7. return reList;
  8. }
  9. //index表示第index个元素,l表示保存了index个元素的排列
  10. //当传进index+1时,l就获得一个index+1个元素的排列
  11. private void find(int[] nums, int index, List<Integer> l){
  12. //至多组成nums.length的长度的一个排列,当index==nums.length时说明生成了一个排列到l中
  13. //保存l到reList
  14. if(index == nums.length){
  15. //保存当前生成的这个排列
  16. reList.add(new ArrayList<>(l));
  17. return;
  18. }
  19. //遍历每一个数字,找到需要添加的第index+1个数字
  20. for(int i=0; i<nums.length; i++){
  21. //l中不存在这个数字,则添加
  22. if(!l.contains(nums[i])){
  23. l.add(nums[i]);
  24. find(nums, index+1, l);
  25. //在排列问题中,元素之间是相互冲突的,因此在回溯时需要对l所存的内容也进行回溯
  26. //回退一个元素,不然l中就始终存在nums[i]了,nums[i]会被重复使用
  27. //回溯状态和变量!
  28. l.remove(l.size()-1);
  29. }
  30. }
  31. return;
  32. }
  33. }

排列问题中:一个数字在不同排列中可以出现多次,因此每次递归 i 从0开始

2.组合问题

77. 组合

递归与回溯 - 图5

递归与回溯 - 图6

组合问题中:前一次被取用元素在下一次中不再取用

  1. class Solution {
  2. private List<List<Integer>> reList = new ArrayList<>();
  3. private List<Integer> tmp = new ArrayList<>();
  4. private List<Boolean> used = new ArrayList<Boolean>();
  5. private int pre = -1;
  6. public List<List<Integer>> combine(int n, int k) {
  7. if(k==0 || n==0) return reList;
  8. List<Integer> list = new ArrayList<>();
  9. for(int i=0; i<=n; i++){
  10. list.add(i);
  11. used.add(false);
  12. }
  13. find(list, k, 1);
  14. return reList;
  15. }
  16. private void find(List<Integer> n, int k, int index){
  17. if(tmp.size() == k){
  18. reList.add(new ArrayList<>(tmp));
  19. return;
  20. }
  21. //组合问题基本模板
  22. for(int i=index; i<n.size(); i++){
  23. tmp.add(n.get(i));
  24. //前一个分支把第i个数及以前的数字的所有情况都考虑了进去,因此从i+1个元素开始考虑
  25. find(n, k, i+1);
  26. //回溯,探寻弹出的元素可以组成的其他组合
  27. //回溯算法中基本上都要
  28. pre = tmp.remove(tmp.size()-1);
  29. }
  30. }
  31. }

剪枝思想

递归与回溯 - 图7

对于n=4,k=2的情况而言,根本不需要去考虑取4,即最后一个元素的情况,因此可以尝试将取4的情况 “剪掉”

对于分析发现:

  • 每次循环,储存组合的list中还有 k-list.size()个空位
  • 所以 [i … n] 中至少要有 k-list.size() 个元素来填补list的空位,于是 i 如何求呢?

    • k-l.size() == 1时, i(最大) == n\ == n-1+1, [n…n]就有1个元素刚好填补
    • k-l.size() == 2时, i(最大)==n-1==n-2+1, [n-1…n] 就有2个元素刚好填补
    • k-l.size() == 3时, i(最大)==n-2==n-3+1, [n-2…n] 3个元素刚好填补
  • 于是推导得 i <= n-(k-l.size())+1 <= n,i 不能大于 n-(k-l.size())+1 ,会导致需要填补的元素变多,空位不足
  1. class Solution {
  2. private List<List<Integer>> reList = new LinkedList<>();
  3. public List<List<Integer>> combine(int n, int k) {
  4. if(k==0 || n==0 || k>n) return reList;
  5. find(n, new ArrayList<>(), k, 1);
  6. return reList;
  7. }
  8. private void find(int n, List<Integer> l, int k, int start){
  9. if(l.size() == k){
  10. reList.add(new ArrayList<>(l));
  11. return;
  12. }
  13. // i<=n-(k-l.size())+1 剪枝条件
  14. // list中还有k-l.size()个空位
  15. // 所以 [i .... n] 中至少要有 k-l.size() 个元素填补,因此问题为 i 取多少时[i .... n]有k-l.size() 个元素?
  16. // k-l.size() == 1时, i(最大)==n==n-1+1, [n...n]就有1个元素刚好填补
  17. // k-l.size() == 2时, i(最大)==n-1==n-2+1, [n-1...n] 就有2个元素刚好填补
  18. // k-l.size() == 3时, i(最大)==n-2==n-3+1, [n-2...n] 3个元素刚好填补
  19. //因此 i <= n-(k-l.size())+1 <= n
  20. //i最大等于n-(k-l.size())+1,因为如果i比n-(k-l.size())+1还大的话,list中就没有足够的空位了
  21. for(int i=start; i<=n-(k-l.size())+1; i++){
  22. l.add(i);
  23. //前一个分支把第i个数及以前的数字的所有情况都考虑了进去,因此从i+1个元素开始考虑
  24. find(n, l, k, i+1);
  25. l.remove(l.size()-1);
  26. }
  27. }
  28. }

39. 组合总和

递归与回溯 - 图8

  1. class Solution {
  2. private Set<List<Integer>> set = new HashSet<>();
  3. private List<List<Integer>> reList = new LinkedList<>();
  4. private List<Integer> tmp = new ArrayList<>();
  5. public List<List<Integer>> combinationSum(int[] candidates, int target) {
  6. if(candidates.length == 0) return reList;
  7. find(candidates, target, 0, 0);
  8. reList.addAll(set);
  9. return reList;
  10. }
  11. private void find(int[] candidates, int tartget, int index, int sum){
  12. if(sum > tartget) return;
  13. if(sum==tartget || index>candidates.length){
  14. set.add(new ArrayList<>(tmp));
  15. return;
  16. }
  17. //从index开始,每一位都重复添加,看是否与target相等
  18. for(int i=index; i<candidates.length; i++){
  19. tmp.add(candidates[i]);
  20. //传入i,重复相加
  21. find(candidates, tartget, i, sum+candidates[i]);
  22. tmp.remove(tmp.size()-1);
  23. }
  24. }
  25. }

40. 组合总和 II

递归与回溯 - 图9

  1. class Solution {
  2. private Set<List<Integer>> set = new HashSet<>();
  3. private List<List<Integer>> reList = new LinkedList<>();
  4. private List<Integer> tmp = new ArrayList<>();
  5. private Set<Integer> canSet = new HashSet<>();
  6. private int pre = -1;
  7. public List<List<Integer>> combinationSum2(int[] candidates, int target) {
  8. Arrays.sort(candidates); //排序,相邻大小数挨在一起
  9. find(candidates, target, 0, 0);
  10. reList.addAll(set);
  11. return reList;
  12. }
  13. //每个数只能选择一次, 不允许同级出现同样大小的元素,但不同层可以
  14. private void find(int[] candidates, int tartget, int index, int sum){
  15. if(sum > tartget) return;
  16. if(sum==tartget){
  17. set.add(new ArrayList<>(tmp));
  18. return;
  19. }
  20. for(int i=index; i<candidates.length; i++){
  21. //用pre记录删除了的元素,表示已经使用过,不再使用,剪枝
  22. if(pre == candidates[i]) continue;
  23. tmp.add(candidates[i]);
  24. //一个数只能用一次,i+1
  25. find(candidates, tartget, i+1, sum+candidates[i]);
  26. pre = tmp.remove(tmp.size()-1);
  27. }
  28. }
  29. }

216. 组合总和 III

递归与回溯 - 图10

  1. class Solution {
  2. private Set<List<Integer>> reSet = new HashSet<>();
  3. private List<Integer> tmp = new ArrayList<>();
  4. private int[] arr = new int[]{1,2,3,4,5,6,7,8,9};
  5. private boolean[] used = new boolean[]{false,false,false,false,false,false,false,false,false};
  6. public List<List<Integer>> combinationSum3(int k, int n) {
  7. find(n,k,0, 0);
  8. List<List<Integer>> reList = new ArrayList<>(reSet);
  9. return reList;
  10. }
  11. private void find(int target, int k, int index, int sum){
  12. if(sum > target || tmp.size() > k) return;
  13. if(sum == target && tmp.size()==k){
  14. reSet.add(new ArrayList<>(tmp));
  15. return;
  16. }
  17. for(int i=index; i<9; i++){
  18. if(!used[i]){
  19. tmp.add(arr[i]);
  20. used[i] = true;
  21. find(target, k, i, sum+arr[i]);
  22. used[i] = false;
  23. tmp.remove(tmp.size()-1);
  24. }
  25. }
  26. }
  27. }

组合题规律总结

当题干包含:

  • 递归与回溯 - 图11时,使用Set集合去重

  • 递归与回溯 - 图12 使用辅助数字判断哪个数字已经被使用过,在生成一个组合后将数字和存储组合的列表回溯 (如216,面试题08)

  • 递归与回溯 - 图13循环时 i 以传入第index位基位,递归时重复传入当前 i 表示可以重复使用 (如39题)

  • 递归与回溯 - 图14循环时 i 以传入第index位基位,递归时传入 i+1,表示前 i 个数字已经存在当前组合中 (这种情况下可以考虑剪枝提高效率) (如40题)

二维平面上的回溯法

比如最常见的迷宫问题

79. 单词搜索

递归与回溯 - 图15

递归与回溯 - 图16

基于传入的坐标,对当前坐标的四周进行试探,当满足条件是进入坐标,继续试探,被试探过的坐标应该标记为visited,当该坐标四周试探完成不满足条件后,应该将vitised回溯为未访问状态,以便让其相邻位置进行试探

  1. class Solution {
  2. //设置偏移量数组的技巧在二维数组问题中经常使用
  3. //4个方位,每个一维数组中的第一个元素代表startX的移动,第二个元素代表startY的移动
  4. private int map[][] = new int[][]{ {-1,0}, //上
  5. {0,1}, //右
  6. {1,0}, //下
  7. {0,-1}}; //左
  8. private Boolean[][] visited;
  9. public boolean exist(char[][] board, String word) {
  10. if(board.length==0 || word==null) return false;
  11. visited = new Boolean[board.length][board[0].length];
  12. for(int i=0; i<board.length; i++){
  13. for(int j=0; j<board[i].length; j++){
  14. visited[i][j] = false;
  15. }
  16. }
  17. for(int i=0; i<board.length; i++){
  18. for(int j=0; j<board[i].length; j++){
  19. //每次从[i][j]出发,寻找相应的字符串
  20. if (searchWord(board, word, 0, i, j)) {
  21. return true;
  22. }
  23. }
  24. }
  25. return false;
  26. }
  27. //每次查找word的第index个元素
  28. //从board[startX][startY]开始,寻找word[index...word.length()]
  29. private boolean searchWord(char[][]board, String word, int index, int startX, int startY){
  30. //当前所处的board位置是否与字符串最后一位相同
  31. if(index == word.length()-1){
  32. return board[startX][startY] == word.charAt(index);
  33. }
  34. //元素相等时,继续搜索
  35. if(board[startX][startY] == word.charAt(index)){
  36. //设置为已经访问过该元素
  37. visited[startX][startY] = true;
  38. //从startX,startY开始朝四个方向出发进行搜索
  39. for(int i=0; i<4; i++){
  40. int newX = startX + map[i][0];
  41. int newY = startY + map[i][1];
  42. //newX,newY不能越界 && board[newX][newY]没有被访问过
  43. if((newX>=0 && newX<board.length && newY>=0 && newY<board[0].length) &&
  44. !visited[newX][newY]){
  45. if (searchWord(board, word, index+1, newX, newY)) {
  46. return true;
  47. }
  48. }
  49. }
  50. //基于[startX][startY]找遍4个方向都找不到的话,放弃[startX][startY]位置,置位false
  51. visited[startX][startY] = false;
  52. }
  53. return false;
  54. }
  55. }

FloodFill算法(洪泛填充算法)

FloodFill的过程其实就是 DFS 的过程

200. 岛屿数量

递归与回溯 - 图17

但我们需要找到与一个陆地直接相邻,即同属于一个岛屿的其它陆地(递归寻找),进行标记,这样在寻找下一个岛屿的时候才不会重复。

从某个位置开始向外扩散,直到没有相连的陆地,这个过程就是FloodFill的过程

这里的 FloodFill算法 不需要进行状态回溯,因为当递归未结束时找到的所有陆地一定属于了一个岛屿,如果回溯了的话就说明不属于这个岛屿了

  1. class Solution {
  2. private boolean[][] visited;
  3. private int[][] map = new int[][]{{-1,0},{0,1},{1,0},{0,-1}};
  4. private int islands;
  5. public int numIslands(char[][] grid) {
  6. if(grid.length==0) return 0;
  7. visited = new boolean[grid.length][grid[0].length];
  8. for (int i = 0; i < grid.length; i++) {
  9. for(int j = 0; j < grid[i].length; j++){
  10. visited[i][j] = false;
  11. }
  12. }
  13. for (int i = 0; i < grid.length; i++) {
  14. for(int j = 0; j < grid[i].length; j++){
  15. //FloodFill :本质就是一次DFS
  16. //递归终止条件隐含在这,只有陆地才会归入岛屿
  17. //再次循环时,对于同属于这块岛屿的其它陆地就不会再进行查找
  18. if(grid[i][j] == '1' && !visited[i][j]){
  19. islands++;
  20. findIslands(grid, i, j);
  21. }
  22. }
  23. }
  24. return islands;
  25. }
  26. private void findIslands(char[][] grid, int startX, int startY){
  27. for(int i=0; i<4 ;i++) {
  28. //不仅标识是否被访问,同时也标识已经属于一个岛屿了
  29. visited[startX][startY] = true;
  30. int newX = startX+map[i][0];
  31. int newY = startY+map[i][1];
  32. if(newX>=0 && newX<grid.length && newY>=0 && newY<grid[0].length && !visited[newX][newY]){
  33. if(grid[newX][newY] == '1'){
  34. findIslands(grid, newX, newY);
  35. }
  36. }
  37. //这里不需要状态回溯,因为当递归未结束时找到的所有陆地一定属于了一个岛屿,如果回溯了的话就说明不属于这个岛屿了
  38. }
  39. }
  40. }

这里有另外一种更简洁的解法

  1. //思米马赛!
  2. //一次dfs找到一个岛屿
  3. class Solution {
  4. public static void dfs(char[][] grid, int i, int j) {
  5. if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] != '1') return;
  6. else grid[i][j] = '2'; // 将找到的 '1' 标注为 2,防止便利时再被找到
  7. dfs(grid, i, j+1); // 搜索上下左右的 '1'
  8. dfs(grid, i-1, j);
  9. dfs(grid, i+1, j);
  10. dfs(grid, i, j-1);
  11. }
  12. public static int numIslands(char[][] grid) {
  13. int count = 0;
  14. for (int i = 0; i < grid.length; i++) {
  15. for (int j = 0; j < grid[0].length; j++) {
  16. if(grid[i][j] == '1') {
  17. ++count;
  18. dfs(grid, i, j);
  19. }
  20. }
  21. }
  22. return count;
  23. }
  24. }

130. 被围绕的区域

递归与回溯 - 图18

只要与边界上的O相邻的所有O都不会被反转成X,因此可以认为是一个反向的FloodFill,从边界上的每一个O开始递归,将递归到的所有为O的位置标记为true (在对grid遍历时对于X的位置就可以标记为true)。最后将不为true的位置(即不与边界相连)上的所有元素转换成X

  1. class Solution {
  2. int[][] map = new int[][]{{-1,0},{0,1},{1,0},{0,-1}};
  3. boolean[][] visited;
  4. public void solve(char[][] board) {
  5. if(board.length == 0) return;
  6. visited = new boolean[board.length][board[0].length];
  7. for (int i = 0; i < board.length; i++) {
  8. for(int j=0; j<board[i].length; j++){
  9. visited[i][j] = false;
  10. }
  11. }
  12. //找出与边界相连的所有O,以及所有X,标记为true
  13. for (int i = 0; i < board.length; i++) {
  14. for(int j=0; j<board[i].length; j++){
  15. if(board[i][j]=='X') visited[i][j] = true;
  16. //从边界开始进行dfs
  17. if(board[i][j]=='O' && (i==0 || j==0 || i==board.length-1 || j==board[i].length-1)){
  18. dfs(board, i, j);
  19. }
  20. }
  21. }
  22. //没有被标记的就是没有与边界相邻的O了
  23. for (int i = 0; i < visited.length; i++) {
  24. for(int j=0; j<visited[i].length; j++){
  25. if(!visited[i][j]){
  26. board[i][j] = 'X';
  27. }
  28. }
  29. }
  30. }
  31. public void dfs(char[][] board, int startX, int startY){
  32. for(int i=0; i<4; i++){
  33. visited[startX][startY] = true;
  34. int newX = startX+map[i][0];
  35. int newY = startY+map[i][1];
  36. if(newX>0 && newY>0 && newX<board.length && newY<board[0].length && !visited[newX][newY]){
  37. if(board[newX][newY] == 'O'){
  38. dfs(board, newX, newY);
  39. }
  40. }
  41. }
  42. }
  43. }

51. N皇后

递归与回溯 - 图19

递归与回溯 - 图20

递归与回溯 - 图21

以4皇后为例,4*4的范围,每一行都应该有一个皇后,不然其中一行没有皇后,则说明另外一行至少有两个皇后,即违背题意。

每一次在一行中摆放一个皇后,看是否能摆放,如果不能摆放则回到上一行,重新摆放上一行皇后的位置,直到在四行中每一行都摆放了皇后

递归与回溯 - 图22

对第二行而言,位置0和1是不满足条件的位置(和row1冲突),于是剪枝剪去

于是,如何快速判断不合法情况?

  • 竖向:col[i] 表示第 i 列已经被占用
  • 对角线1:dia1[i] 表示对角线1中第 i 个元素被占用
  • 对角线2:dia2[i] 表示对角线2中第 i 个元素被占用

对对角线1而言

递归与回溯 - 图23

发现对角线上的横纵坐标相加是相同的,于是用dia1[x+y] 表示第 i 个对角线被占用很合适

对对角线2而言

递归与回溯 - 图24

对角线上的横纵坐标相减的值是一样的,但是由于是从-3开始的,于是使用 i-j+n-1使其从下标0开始,即 dia2[x-y+n-1]

  1. class Solution {
  2. //存放每一行皇后摆放在第几列的位置
  3. //row[0] 存放 第一行中皇后所在的位置
  4. //row[1] 存放 第二行中皇后所在的位置
  5. private List<Integer> row = new ArrayList<>();
  6. //纵方向是否冲突
  7. private List<Boolean> col = new ArrayList<>();
  8. //两个对角线方向是否冲突
  9. private List<Boolean> dia1 = new ArrayList<>();
  10. private List<Boolean> dia2 = new ArrayList<>();
  11. private List<List<String>> reList = new ArrayList<>();
  12. //n皇后的一个解是一个字符串组成的大小为n的列表
  13. public List<List<String>> solveNQueens(int n) {
  14. for(int i=0; i<n; i++){
  15. col.add(false);
  16. }
  17. for(int i=0; i<2*n-1; i++){
  18. dia1.add(false);
  19. dia2.add(false);
  20. }
  21. find(n, 0);
  22. return reList;
  23. }
  24. //index: 第几行
  25. //在n皇后问题中,摆放第index行的皇后所处位置
  26. private void find(int n, int index){
  27. //index == n,说明得到了一个n皇后问题的解
  28. if(index == n){
  29. //增加一个n皇后问题的解,getOne生成一个.和Q组成的字符串列表
  30. reList.add(getOne(n));
  31. return;
  32. }
  33. for(int i=0; i<n; i++){
  34. //尝试将第index行的皇后摆放在第i列
  35. //如果在纵,以及两个对角线方向都不冲突的话,进行摆放
  36. if(!col.get(i) && !dia1.get(index+i) && !dia2.get(index-i+n-1) ){
  37. row.add(i);
  38. //将对应的列,及两个对角线置位true
  39. col.set(i, true);
  40. dia1.set(index+i, true);
  41. dia2.set(index-i+n-1, true);
  42. //尝试下一行
  43. find(n, index+1);
  44. //回溯
  45. col.set(i, false);
  46. dia1.set(index+i, false);
  47. dia2.set(index-i+n-1, false);
  48. row.remove(row.size()-1);
  49. }
  50. }
  51. }
  52. //将每一行转换为.,并在每一行的相应位置改为Q
  53. private List<String> getOne(int n){
  54. List<String> re = new ArrayList<>();
  55. //n*n的大小
  56. char[][] arr = new char[n][n];
  57. //每个位置初始化为.
  58. for(int i=0; i<n; i++){
  59. for(int j=0; j<n; j++){
  60. arr[i][j] = '.';
  61. }
  62. }
  63. //在每一行的皇后位置置为Q
  64. for(int i=0; i<n; i++){
  65. //row代表每一行中的皇后应该存在的列位置
  66. arr[i][row.get(i)] = 'Q';
  67. //每一行形成的String存入re中
  68. re.add(String.valueOf(arr[i]));
  69. }
  70. return re;
  71. }
  72. }