回溯法其实就是递归,暴力搜索N叉树。做题画一颗N叉树,适当时剪枝。
回溯法能解决的问题:
组合问题:N个数里面按一定规则找出k个数的集合 
排列问题:N个数按一定规则全排列,有几种排列方式 
切割问题:一个字符串按一定规则有几种切割方式 
子集问题:一个N个数的集合里有多少符合条件的子集 
棋盘问题:N皇后,解数独等等 
 
回溯法的基本模版
1 2 3 4 5 6 7 8 9 10 11 12 void  backtracking (参数)      if  (终止条件) {         存放结果;         return ;     }     for  (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {         处理节点;         backtracking (路径,选择列表);          回溯,撤销处理结果     } } 
回溯法抽象为树形结构 后,其遍历过程就是:for循环横向遍历,递归纵向遍历,回溯不断调整结果集 。
组合问题 组合 https://leetcode.cn/problems/combinations/ 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class  Solution  {    public  List<List<Integer>> ans = new  ArrayList <>();     public  Deque<Integer> path = new  ArrayDeque <>();     public  void  backTrait (int  n, int  start, int  k) {         if (path.size()==k){             ans.add(new  ArrayList <>(path));             return ;         }         for (int  i=start; i<=n; ++i){            path.add(i);            backTrait(n, i+1 , k);            path.removeLast();          }     }     public  List<List<Integer>> combine (int  n, int  k)  {         backTrait(n, 1 , k);         return  ans;     } } 
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
解题思路:树形结构遍历、next greater去重
优化思路:如果剩下元素不足以凑齐K个,不用往下遍历。
1 2 3 4 for (int  i=start; i<= n-k+1 +path.size(); ++i){
组合2 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class  Solution  {    public  List<List<Integer>> ans = new  ArrayList <>();     public  Deque<Integer> path = new  ArrayDeque <>();     public  void  backTrack (int  k, int  n, int  start, int  sum) {         if (path.size()==k && sum==n){             ans.add(new  ArrayList <>(path));             return ;         }         else  if (path.size() > k) return ;         for (int  i=start; i<=9 ; ++i){             path.add(i);             sum+=i;             backTrack(k,n, i+1 , sum);             path.removeLast();             sum-=i;         }     }     public  List<List<Integer>> combinationSum3 (int  k, int  n)  {         backTrack(k, n, 1 , 0 );         return  ans;     } } 
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。与上题相比,就是加了一个和为k的限制。
电话号码的数字组合 
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序  返回。
难点在于集合的映射,本题每一个数字代表的是不同集合,也就是求不同集合之间的组合 .
1 2 输入:digits = "23" 输出:["ad","ae","af","bd","be","bf","cd","ce","cf"] 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class  Solution  {         public  Map<Character, String> map = new  HashMap <>(){{             put('2' , "abc" );             put('3' , "def" );             put('4' , "ghi" );             put('5' , "jkl" );             put('6' , "mno" );             put('7' , "pqrs" );             put('8' , "tuv" );             put('9' , "wxyz" );         }};     public  List<String> ans = new  ArrayList <>();     public  void  backTrack (String digits, int  index, StringBuilder path) {        if (path.length() == digits.length()){            ans.add(path.toString());            return ;        }         char  num  =  digits.charAt(index);         String  alphabet  =  map.get(num);         for (int  j=0 ; j<alphabet.length(); ++j){             char  letter  =  alphabet.charAt(j);             path.append(letter);             backTrack(digits, index+1 , new  StringBuilder (path));             path.deleteCharAt(index);         }      }     public  List<String> letterCombinations (String digits)  {         if (digits.isEmpty()) return  ans;         backTrack(digits, 0 , new  StringBuilder ());         StringBuilder  sb  =  new  StringBuilder ();         return  ans;     } } 
组合总和 给你一个 无重复元素  的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合  ,并以列表形式返回。你可以按 任意顺序  返回这些组合。
candidates 中的 同一个  数字可以 无限制重复被选取  。如果至少一个数字的被选数量不同,则两种组合是不同的。 
1 2 输入:candidates = [2,3,6,7], target = 7 输出:[[2,2,3],[7]] 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 vector<vector<int >> ret;     vector<int > path;     void  backtrack (vector<int >& candidates, int  target, int  start)          if (target<0 ) return ;         else  if  (target==0 ){             ret.push_back (path);             return ;         }         for (int  i=start; i<candidates.size (); ++i){             int  num = candidates[i];             if (target-num<0 ) break ;              path.push_back (num);             backtrack (candidates, target-num, i);              path.pop_back ();         }     }     vector<vector<int >> combinationSum (vector<int >& candidates, int  target) {         sort (candidates.begin (), candidates.end ());          backtrack (candidates, target, 0 );         return  ret;       } 
本题与1.2组合2的区别在于,同一个数字可以无限制重复选取。故区别在递归时,startindex是从i开始(意味着本数字可以重复选取),而不是从i+1开始。
在求和问题中,排序之后加剪枝是常见的套路! 
组合总和2 给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次  。
1 2 3 4 5 6 输入: candidates = [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ] 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 vector<vector<int >> ret;   vector<int > path;   void  backtrack (vector<int >& candidates, int  target, int  start, vector<int >& visited)        if (target<0 ) return ;       else  if  (target==0 ){           ret.push_back (path);           return ;       }       for (int  i=start; i<candidates.size (); ++i){           int  num = candidates[i];           if (target-num<0 ) break ;                     if (i>0  && candidates[i]==candidates[i-1 ] && visited[i-1 ]==0 )              continue ;            path.push_back (num);           visited[i]= 1 ;           backtrack (candidates, target-num, i+1 , visited);            visited[i]= 0 ;           path.pop_back ();       }   }   vector<vector<int >> combinationSum2 (vector<int >& candidates, int  target) {       vector<int > visited (candidates.size(), 0 )  ;       sort (candidates.begin (), candidates.end ());        backtrack (candidates, target, 0 , visited);       return  ret;     } 
这道题与上一题的区别在于集合中元素是否重复,并且上一题中的元素可以重复选取,这题每个元素只能用一次。
要去重,否则1,2,2的组合会出现3次。去重是难点!
为了讲解这个去重问题,我自创了两个词汇,“树枝去重”和“树层去重” 。
都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上“使用过”,一个维度是同一树层上“使用过”。
 
used[i - 1] == true,说明同一树枝 candidates[i - 1]使用过 
used[i - 1] == false,说明同一树层 candidates[i - 1]使用过 
 
1 2  if (i>start && candidates[i]==candidates[i-1 ])  
对于组合问题,什么时候需要startIndex呢? 
如果是一个集合来求组合的话,就需要startIndex,如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex.
 
切割 分割回文串 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串  。返回 s 所有可能的分割方案。
1 2 输入:s = "aab" 输出:[["a","a","b"],["aa","b"]] 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 vector<vector<string>> ret;     vector<string> path;     bool  is_hui (string& s, int  l, int  r)          if (l>r) return  false ;         while (l<r){             if (s[l]!=s[r]) return  false ;             ++l;             --r;         }         return  true ;     }     void  backtrack (string& s, int  start)          if (start==s.size ()){             ret.push_back (path);             return  ;         }         for (int  i=start; i<s.size ();++i){             if (is_hui (s, start, i)){                 path.push_back (s.substr (start, i-start+1 ));             }             else {                 continue ;             }             backtrack (s, i+1 );              path.pop_back ();         }     }     vector<vector<string>> partition (string s) {         backtrack (s, 0 );         return  ret;     } 
回溯法怎么应用于切割问题?其实只要想象长度为n的串,分为若干段,每段长度可以相同,各段长度和为n。这不就是组合总和的思路嘛。candidate就是从1到n,target就是n,元素可以重复选取。
复原IP地址 https://leetcode.cn/problems/restore-ip-addresses/ 
子集问题 子集问题 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
示例: 输入: nums = [1,2,3] 输出: [ [3],  [1],  [2],  [1,2,3],  [1,3],  [2,3],  [1,2],  [] ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class  Solution  {    public  List<List<Integer>> res = new  ArrayList<>();     LinkedList<Integer> path = new  LinkedList<>();     public  void  backTrack (int [] nums, int  start)          res.add (new  ArrayList (path));         if (start==nums.length) return ;         for (int  i=start; i<nums.length; ++i){             path.add (nums[i]);             backTrack (nums, i+1 );             path.removeLast ();         }     }     public  List<List<Integer>> subsets (int [] nums) {         backTrack (nums, 0 );         return  res;     } } 
子集问题怎么解?在之前的问题中,我们都是收集叶子节点的结果。现在求子集,我们要收集每一个节点的结果! 
收集结果的代码放在递归的第一行,收集本身。注意空子集在第一次调用时收集。 
子集问题2 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
1 2 - 输入: [1,2,2] - 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ] 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class  Solution  {private :    vector<vector<int >> result;     vector<int > path;     void  backtracking (vector<int >& nums, int  startIndex)           result.push_back (path);         for  (int  i = startIndex; i < nums.size (); i++) {                          if  (i > startIndex && nums[i] == nums[i - 1 ] ) {                  continue ;             }             path.push_back (nums[i]);             backtracking (nums, i + 1 );             path.pop_back ();         }     } public :    vector<vector<int >> subsetsWithDup (vector<int >& nums) {         result.clear ();         path.clear ();         sort (nums.begin (), nums.end ());          backtracking (nums, 0 );         return  result;     } }; 
这题与上题的区别在于集合可以包含重复元素,在选子集时会出现重复的情况,所以需要去重。
如何去重?树层去重(需要排序)。
递增子序列 给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
1 2 - 输入: [4, 7, 6, 7] - 输出:[[4,7],[4,7,7],[4,6],[4,6,7],[7,7],[6,7]] 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 vector<vector<int >> ret;   vector<int > path;   void  backtrack (vector<int >& nums, int  start)      	       if (path.size ()>=2 )            ret.push_back (path);              if (start>=nums.size ()) return ;              unordered_set<int > uset;       for (int  i=start; i<nums.size ();++i){               if ((!path.empty () && nums[i]<path.back ())               ||uset.find (nums[i])!=uset.end ())                 continue ;            uset.insert (nums[i]);            path.push_back (nums[i]);             backtrack (nums, i+1 );           path.pop_back ();                  }   }   vector<vector<int >> findSubsequences (vector<int >& nums) {       backtrack (nums, 0 );       return  ret;   } 
这道题类似子集问题2,只不过要求找到的子集长度至少为2,并且递增。
本题不能使用之前的去重逻辑!求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。
所以需要利用set去重。
这也是需要注意的点,unordered_set<int> uset; 是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层! 
排列 全排列问题 给定一个 没有重复 数字的序列,返回其所有可能的全排列。
1 2 - 输入: [1,2,3] - 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ] 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class  Solution  {public :    vector<vector<int >> result;     vector<int > path;     void  backtracking  (vector<int >& nums, vector<bool >& used)                    if  (path.size () == nums.size ()) {             result.push_back (path);             return ;         }         for  (int  i = 0 ; i < nums.size (); i++) {             if  (used[i] == true ) continue ;              used[i] = true ;             path.push_back (nums[i]);             backtracking (nums, used);             path.pop_back ();             used[i] = false ;         }     }     vector<vector<int >> permute (vector<int >& nums) {         result.clear ();         path.clear ();         vector<bool > used (nums.size(), false )  ;         backtracking (nums, used);         return  result;     } }; 
想象一颗N叉树,不能重复选取元素,startindex也没用了,统一从0开始,借助used数组。
大家此时可以感受出排列问题的不同: 
每层都是从0开始搜索而不是startIndex 
需要used数组记录path里都放了哪些元素了 
 
全排列问题2 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
1 2 - 输入:nums = [1,1,2] - 输出: [[1,1,2], [1,2,1], [2,1,1]] 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class  Solution  {private :    vector<vector<int >> result;     vector<int > path;     void  backtracking  (vector<int >& nums, vector<bool >& used)                    if  (path.size () == nums.size ()) {             result.push_back (path);             return ;         }         for  (int  i = 0 ; i < nums.size (); i++) {                                                    if  (i > 0  && nums[i] == nums[i - 1 ] && used[i - 1 ] == false ) {                 continue ;             }             if  (used[i] == false ) {                 used[i] = true ;                 path.push_back (nums[i]);                 backtracking (nums, used);                 path.pop_back ();                 used[i] = false ;             }         }     } public :    vector<vector<int >> permuteUnique (vector<int >& nums) {         result.clear ();         path.clear ();         sort (nums.begin (), nums.end ());          vector<bool > used (nums.size(), false )  ;         backtracking (nums, used);         return  result;     } }; 
这题需要去重了,否则长度为3的数组全排列应该有3x2x1=6个。
如何去重?sort后再树层去重?
中途休息 性能分析 之前并没有分析各个问题的时间复杂度和空间复杂度,这次来说一说。
子集问题分析: 
时间复杂度:$O(n × 2^n)$,因为每一个元素的状态无外乎取与不取,所以时间复杂度为$O(2^n)$,构造每一组子集都需要填进数组,又有需要$O(n)$,最终时间复杂度:$O(n × 2^n)$。???? 
空间复杂度:$O(n)$,递归深度为n,所以系统栈所用空间为$O(n)$,每一层递归所用的空间都是常数级别,注意代码里的result和path都是全局变量,就算是放在参数里,传的也是引用,并不会新申请内存空间,最终空间复杂度为$O(n)$。 
 
排列问题分析: 
时间复杂度:$O(n!)$,这个可以从排列的树形图中很明显发现,每一层节点为n,第二层每一个分支都延伸了n-1个分支,再往下又是n-2个分支,所以一直到叶子节点一共就是 n * n-1 * n-2 * ….. 1 = n!。每个叶子节点都会有一个构造全排列填进数组的操作(对应的代码:result.push_back(path)),该操作的复杂度为$O(n)$。所以,最终时间复杂度为:n * n!,简化为$O(n!)$。 
空间复杂度:$O(n)$,和子集问题同理。 
 
组合问题分析: 
时间复杂度:$O(n × 2^n)$,组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。 
空间复杂度:$O(n)$,和子集问题同理。 
 
一般说道回溯算法的复杂度,都说是指数级别的时间复杂度,这也算是一个概括吧! 
关于去重 
子集问题2中使用了普通去重。
在全排列2中就使用了used数组去重,set集合去重写法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class  Solution  {private :    vector<vector<int >> result;     vector<int > path;     void  backtracking  (vector<int >& nums, vector<bool >& used)           if  (path.size () == nums.size ()) {             result.push_back (path);             return ;         }         unordered_set<int > uset;          for  (int  i = 0 ; i < nums.size (); i++) {             if  (uset.find (nums[i]) != uset.end ()) {                 continue ;             }             if  (used[i] == false ) {                 uset.insert (nums[i]);                  used[i] = true ;                 path.push_back (nums[i]);                 backtracking (nums, used);                 path.pop_back ();                 used[i] = false ;             }         }     } public :    vector<vector<int >> permuteUnique (vector<int >& nums) {         result.clear ();         path.clear ();         sort (nums.begin (), nums.end ());          vector<bool > used (nums.size(), false )  ;         backtracking (nums, used);         return  result;     } }; 
需要注意的是:使用set去重的版本相对于used数组的版本效率都要低很多 
主要是因为程序运行的时候对unordered_set 频繁的insert,unordered_set需要做哈希映射(也就是把key通过hash function映射为唯一的哈希值)相对费时间,而且insert的时候其底层的符号表也要做相应的扩充,也是费时的。
使用set去重,不仅时间复杂度高了,空间复杂度也高了 ,在本周小结!(回溯算法系列三) (opens new window) 中分析过,组合,子集,排列问题的空间复杂度都是O(n),但如果使用set去重,空间复杂度就变成了O(n^2),因为每一层递归都有一个set集合,系统栈空间是n,每一个空间都有set集合。
其他问题 括号生成 https://leetcode.cn/problems/generate-parentheses/submissions/ 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class  Solution  {    public  List<String> ans = new  LinkedList ();     public  void  backTrack (int  n, StringBuilder cur, int  l_n, int  r_n) {         if (n==0 ){             if (l_n==r_n) ans.add(cur.toString());                      return ;         }                  if (l_n < r_n) return ;                  cur.append('(' );         backTrack(n-1 , cur, 1 +l_n, r_n);         cur.deleteCharAt(cur.length()-1 );                  cur.append(')' );         backTrack(n-1 , cur, l_n, 1 +r_n);         cur.deleteCharAt(cur.length()-1 );     }     public  List<String> generateParenthesis (int  n)  {         backTrack(2 *n, new  StringBuilder (), 0 , 0 );         return  ans;      } } 
这里的技巧在于用左右括号的数量来判断当前生成括号是否合法。
解数独 https://leetcode.cn/problems/sudoku-solver 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class  Solution  {    public  boolean  isValid (char [][] board, int  r, int  c, char  ch) {         for (int  i=0 ; i<9 ; ++i){             if (board[r][i]==ch) return  false ;             if (board[i][c]==ch) return  false ;             if (board[3 *(r/3 )+i/3 ][3 *(c/3 )+i%3 ] == ch) return  false ;         }         return  true ;     }     public  boolean  backTrack (char [][] board, int  i, int  j) {                  if (j==9 ) return  backTrack(board, i+1 , 0 );                  if (i==9 ) return  true ;         if (board[i][j] != '.' )             return  backTrack(board, i, j+1 );         else {                          for (char  k='1' ; k<='9' ; ++k){                 if (isValid(board, i, j, k)){                     board[i][j] = k;                     if (backTrack(board, i, j+1 ))                          return  true ;                     board[i][j] = '.' ;                 }             }         }         return  false ;     }     public  void  solveSudoku (char [][] board)  {        backTrack(board, 0 , 0 );      } } 
代码看着简答,优化可不少:
1、 3x3宫格的有效性判断
2、先做有效性判断,再赋值这个格子;
2、backTrack的返回值是boolean且递归时用if判断,找到一个可行解就直接返回
雀魂启动 https://www.nowcoder.com/practice/448127caa21e462f9c9755589a8f2416 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import  java.util.*;public  class  Main  {    static  boolean  flag;     public  static  void  main (String[] args)  {         Scanner  cin  =  new  Scanner (System.in);         int [] nums = new  int [10 ];         for  (int  i  =  0 ; i < 13 ; i++) {             nums[cin.nextInt()]++;         }         for  (int  i  =  1 ; i <= 9 ; i++) {             if  (nums[i] <= 3 ) {                 int [] card = Arrays.copyOf(nums, nums.length);                  card[i]++;                 if  (isHu(card, 1 , 0 , 0 )) {                     flag = true ;                     System.out.print(i + " " );                 }             }         }         if (flag == false )              System.out.print(0 );     }     public  static  boolean  isHu (int [] card, int  start, int  quetou, int  lian) {         if (start>=10 ){                          if (quetou==1  && lian==4 ) return  true ;             else                    return  false ;         }                  if (card[start]>=2  && quetou==0 ){             card[start] -=2 ;             if (isHu(card, start, 1 , lian)) return  true ;             card[start] +=2 ;         }                  if (card[start]>=3 ){             card[start] -=3 ;             if (isHu(card, start, quetou, lian+1 )) return  true ;              card[start] +=3 ;         }                  if  (start + 2  <= 9  && card[start] > 0  && card[start + 1 ] > 0  && card[start + 2 ] > 0 ){             card[start]--;             card[start + 1 ]--;             card[start + 2 ]--;             if (isHu(card, start, quetou, lian+1 )) return  true ;             card[start]++;             card[start + 1 ]++;             card[start + 2 ]++;         }                  if (isHu(card, 1 +start, quetou, lian)) return  true ;         return  false ;     } } 
思路剖析:回溯法
1、手头有13张牌,向其中添加1张牌,看能否和牌;
2、遍历1-9大小的牌,每种数字的牌依次尝试用作雀头、顺子、刻子。
N皇后 https://leetcode.cn/problems/n-queens/submissions/ 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class  Solution  {    List<List<String>> ans = new  ArrayList <>();     public  void  backTrack (char [][] arrs, int  n, int  row) {                  if (row == n){                          List<String> tmp = new  ArrayList <>();             for (char [] arr: arrs){                 tmp.add(new  String (arr));             }             ans.add(tmp);             return ;         }                   for (int  col=0 ; col<n; ++col){             if (isValid(arrs, row, col)){                 arrs[row][col] = 'Q' ;                 backTrack(arrs, n, 1 +row);                 arrs[row][col] = '.' ;             }         }     }     public  boolean  isValid (char [][] arrs, int  row, int  col) {         int  n  = arrs.length;         for (int  i=0 ; i<arrs.length; ++i){             if (arrs[row][i]=='Q' ) return  false ;             if (arrs[i][col]=='Q' ) return  false ;         }                  for  (int  i=row-1 , j=col-1 ; i>=0  && j>=0 ; i--, j--) {             if  (arrs[i][j] == 'Q' ) {                 return  false ;             }         }                  for  (int  i=row-1 , j=col+1 ; i>=0  && j<=n-1 ; i--, j++) {             if  (arrs[i][j] == 'Q' ) {                 return  false ;             }         }         return  true ;     }     public  List<List<String>> solveNQueens (int  n)  {         char [][] arrs  = new  char [n][n];         for (int  i=0 ; i<n; ++i) Arrays.fill(arrs[i], '.' );         backTrack(arrs, n, 0 );         return  ans;     } } 
几个注意点:
1、 用二维字符矩阵模拟棋盘,最后再转化为List<List<String>> 
2、 搜索时有顺序,先按行(自上而下),确定行后自左至右确定列
3、isValid先对未填时进行校验,而且只校验上半部分
–优化解法–:利用boolean数组表示已经占用的直线