排列组合和回溯算法

来源:互联网 发布:安装阿里云的yum源 编辑:程序博客网 时间:2024/05/22 07:56

排列组合

排列组合通常用于在字符串或序列的排列和组合中,其特点是固定的解法和统一的代码风格。通常有两种方法:第一种是类似动态规划的分期摊还的方式,即保存中间结果,依次附上新元素,产生新的中间结果;第二种是递归法,通常是在递归函数里,使用for循环,遍历所有排列或组合的可能,然后在for循环语句内调用递归函数。

回溯

回溯算法也叫试探法,它是一种系统地搜索问题的解的方法。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来
,换一条路再试。用回溯算法解决问题的一般步骤为:
1、定义一个解空间,它包含问题的解。
2、利用适于搜索的方法组织解空间。
3、利用深度优先法搜索解空间。
4、利用限界函数避免移动到不可能产生解的子空间。
问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性。

方案一
对于长度为n的数组,我们需要依次确认每个位置上的元素。初始问题:从a[0]开始依次确认n个元素。如果我们确认a[0]之后,问题就变成从a[1]开始从剩余元素中选择一个元素确定a[1]。每次选择都有多种可能性,我们依次尝试(尝试后还原),并递归解决选择之后的产生的子问题,并定义出口。 
[java] view plain copy
  1.   public List<List<Integer>> permute(int[] nums) {   
  2.     ArrayList<Integer> numsArray=new ArrayList<Integer>();  
  3.     for(int i:nums){  
  4.         numsArray.add(i);  
  5. }  
  6.     Collections.sort(numsArray);  
  7.     List<List<Integer>> res=new ArrayList<List<Integer>>();  
  8.     solve(res,numsArray,0);  
  9. return res;  
  10.   }  
  11.   private void solve(List<List<Integer>> res,ArrayList<Integer> nums,int index){  
  12.     if(index>=nums.size()){  
  13.         List<Integer> permutation=new ArrayList<Integer>(nums);   
  14.         res.add(permutation);  
[java] view plain copy
  1.     }  
  2.     for(int i=index;i<=nums.size()-1;i++){  
  3.         Collections.swap(nums, i, index);  
  4.         solve(res,nums,index+1);  
  5.         Collections.swap(nums, i, index);  
  6.     }  
  7. }  
方案二
用一个标记数组标记元素是否已经使用过,每次从剩余元素中尝试添加新元素到临时解中,当没有新的元素可添加时,临时解就为最终的一个解。此方案比方法一要更加直观。

[java] view plain copy
  1.   public List<List<Integer>> permute(int[] nums) {   
  2.     ArrayList<Integer> numsArray=new ArrayList<Integer>();  
  3.     for(int i:nums){  
  4.         numsArray.add(i);  
  5. }  
  6.     boolean[] used=new boolean[numsArray.size()];  
  7.     Collections.sort(numsArray);  
  8.     List<List<Integer>> res=new ArrayList<List<Integer>>();  
  9.     ArrayList<Integer> subSet=new ArrayList<Integer>();  
  10.     solve(res,numsArray,subSet,used);  
  11. return res;  
  12.   }  
  13.   private void solve(List<List<Integer>> res,ArrayList<Integer> nums,ArrayList<Integer> subSet,boolean[] used){  
  14.     if(subSet.size()==nums.size()){  
  15.         ArrayList<Integer> clone=new ArrayList<Integer>(subSet);  
  16.         res.add(clone);  
  17.         return;  
  18.     }  
  19.     for(int i=0;i<nums.size();i++){  
  20.         if(used[i])continue;//不能重复使用  
  21.         subSet.add(nums.get(i));//加入新元素,并递归调用下一个元素  
  22.         used[i]=true;  
  23.         solve(res,nums,subSet,used);  
  24.         subSet.remove(subSet.size()-1);//还原  
  25.         used[i]=false;  
  26.     }  
  27.   }  

分析
相比上一个问题,元素可以重复,因此我们应该在算法中增加相应的判断来避免重复结果。例如元素[1,2,2,3,3,3],在选择添加第一个新元素时,只考虑第一个1,第一个2,第一个3。注:仔细理清楚避免重复的逻辑,后面会多次用到。
[java] view plain copy
  1.   public List<List<Integer>> permuteUnique(int[] nums) {   
  2.     ArrayList<Integer> numsArray=new ArrayList<Integer>();  
  3.     for(int i:nums){  
  4.         numsArray.add(i);  
  5. }  
  6.     boolean[] used=new boolean[numsArray.size()];  
  7.     Collections.sort(numsArray);  
  8.     List<List<Integer>> res=new ArrayList<List<Integer>>();  
  9.     ArrayList<Integer> subSet=new ArrayList<Integer>();  
  10.     solve(res,numsArray,subSet,used);  
  11. return res;  
  12.   }  
  13.   private void solve(List<List<Integer>> res,ArrayList<Integer> nums,ArrayList<Integer> subSet,boolean[] used){  
  14.     if(subSet.size()==nums.size()){  
  15.         ArrayList<Integer> clone=new ArrayList<Integer>(subSet);  
  16.         res.add(clone);  
  17.         return;  
  18.     }  
  19.     for(int i=0;i<nums.size();i++){  
  20.         //对于剩余可选元素,那些相同的元素,我们只考虑第一个  
  21.         //如果前一个元素与当前元素相同,且前一个元素已经使用了(之前的选择),说明当前元素是“剩余可选元素中所有与当前元素相同的元素”的第一个,则可以选择  
  22.         //如果前一个元素与当前元素相同,但是前一个元素没有使用,说明在此次选择过程中,已经有相同的元素尝试选择过然后还原了,因此将当前元素忽略  
  23.         if(used[i]||(i>0&&!used[i-1]&&nums.get(i).equals(nums.get(i-1)))) continue;  
  24.         subSet.add(nums.get(i));//加入新元素,并递归调用下一个元素  
  25.         used[i]=true;  
  26.         solve(res,nums,subSet,used);  
  27.         subSet.remove(subSet.size()-1);//还原  
  28.         used[i]=false;  
  29.     }  
  30.   }  

分析
我们将排列看成一个n位整数,下一个排列组合就是当前整数增加并且保证增量最小。为了保证增量最小,因此我们需要保证变化的范围尽量限制在低位。因此我们采用如下策略:
1、从后往前寻找第一组相邻元素a[i]<a[i+1]。如果没找到,说明当前序列递减(最大整数),下一个排列只需将序列逆转成最小。如果找到,执行2.
2、为了让变化范围最小化,我们需要将a[i]后面“大于a[i]且最接近a[i]的元素”与a[i]交换,交换后的a[i]后面依然是递减序列,为了进一步减小增量,我们将a[i]后面的元素逆序,得到如下算法。
[java] view plain copy
  1. public void nextPermutation(int[] nums) {  
  2.     int index=nums.length-1;  
  3.     //寻找第一对非递减序列  
  4.     while(index-1>=0&&nums[index-1]>=nums[index]) index--;   
  5.     if(index==0){    
  6.         reverse(nums,0,nums.length-1);  
  7.         return;  
  8.     }  
  9.     int smallerIndex=index-1,change=index;  
  10.     //寻找恰当交换元素  
  11.     while(change+1<nums.length&&nums[change+1]>nums[smallerIndex])change++;  
  12.     int t=nums[smallerIndex];nums[smallerIndex]=nums[change];nums[change]=t;  
  13.     reverse(nums,index,nums.length-1);  
  14. }  
  15. private void reverse(int[] nums,int begin,int end){  
  16.     int t;  
  17.     while(begin<end){  
  18.         t=nums[begin];nums[begin]=nums[end];nums[end]=t;  
  19.         begin++;end--;  
  20.     }  
  21. }  


分析
方案一:我们可以采用暴力枚举,调用k-1次nextPermutation。我们只需要第k个排列,但是我们却计算了前k个,比较耗时。
方案二:n个不同元素的排列种数位n!,我们将[1,2,3,4,5]变换成[2,1,3,4,5]需要多少次变换呢?答案是4!次。
证明:[1,2,3,4,5]变换成[1,5,4,3,2]需要4!-1次,[1,5,4,3,2]变换成[2,1,3,4,5]需要一次,共4!次。
同理:[2,1,3,4,5]变换成[2,3,1,4,5]需要经过3!次变换。
综上所述:将a[i]与“其后大于a[i]且最接近a[i]的元素”进行交换,即代表(n-i)!,1<=i<=n(在这里下标从1开始)次变换。
因此第k个排列,即为初始排列变换k-1次。得到如下算法
[java] view plain copy
  1. public String getPermutation(int n, int k) {  
  2.     int[] arr=new int[n+1];  
  3.     for(int i=1;i<=n;i++){  
  4.         arr[i]=i;  
  5.     }  
  6.     k=(k-1)%factorial(n);    
  7.     int index=1;  
  8.     while(k>0){  
  9.         if(k>=factorial(n-index)){   
  10.             int change=index+1;  
  11.             while(arr[change]<arr[index])change++;  
  12.             int t=arr[index];arr[index]=arr[change];arr[change]=t;  
  13.             k-=factorial(n-index);  
  14.         }else{   
  15.             index++;  
  16.         }  
  17.     }  
  18.     StringBuilder res=new StringBuilder();  
  19.     for(int i=1;i<=n;i++){   
  20.         res.append(arr[i]);  
  21.     }   
  22.     return res.toString();  
  23. }  
  24. private int factorial(int n){  
  25.     int res=1;  
  26.     for(int i=1;i<=n;i++){  
  27.         res*=i;  
  28.     }   
  29.     return res;  
  30. }  

 分析

我们采用分期摊还的方法,从数字字符串的第一个字符开始扫描,记录之前数字产生的所有组合,然后将当前数字映射的字符附加到之前产生的所有组合中,产生新的结果集。

[java] view plain copy
  1. public List<String> letterCombinations(String digits) {  
  2.      String[] strMap={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};  
  3.      ArrayList<String> res=new ArrayList<String>();  
  4.      if(digits.equals("")||digits==nullreturn res;  
  5.      res.add("");  
  6.      for(int i=0;i<digits.length();i++){   
  7.         String map=strMap[digits.charAt(i)-'0'];   
  8.         if(map.equals("")){   
  9.             continue;  
  10.         }  
  11.         ArrayList<String> t=new ArrayList<String>();  
  12.         for(int j=0;j<map.length();j++){  
  13.             for(String str:res){  
  14.                 t.add(str+map.charAt(j));  
  15.             }  
  16.         }  
  17.         res=t;  
  18.      }  
  19.      return res;  
  20.  }  

分析
类似全排列,采用分期摊还方法,不过需要注意过滤重复组合。
[java] view plain copy
  1. public List<List<Integer>> combine(int n, int k) {  
  2.     List<List<Integer>> res=new ArrayList<List<Integer>>();  
  3.     int[] nums=new int[n];  
  4.     ArrayList<Integer> r=new ArrayList<Integer>();  
  5.     boolean[] used=new boolean[n];  
  6.     for(int i=0;i<n;i++)  
  7.         nums[i]=i+1;  
  8.     solve(res,r,nums,k,used);  
  9.     return res;  
  10. }  
  11. public void solve(List<List<Integer>> res,ArrayList<Integer> r,int[] nums,int k,boolean[] used){  
  12.     if(r.size()==k){  
  13.         ArrayList<Integer> clone=new ArrayList<Integer>(r);  
  14.         res.add(clone);  
  15.         return ;  
  16.     }  
  17.     int index=r.size();  
  18.     for(int i=index;i<nums.length;i++){  
  19.         if(used[i]||(r.size()!=0&&nums[i]<r.get(r.size()-1)))continue;  
  20.         r.add(nums[i]);  
  21.         used[i]=true;  
  22.         solve(res,r,nums,k,used);  
  23.         r.remove(r.size()-1);  
  24.         used[i]=false;  
  25.           
  26.     }  
  27. }  

分析
先将候选数组排序,我们将问题理解为从a[0]开始选择整数,使得和为target。如果我们选择了a[0],那么问题变换成从a[0]开始选择整数,使得和为target-a[0](因为可以重复选择)。如果没有选择a[0],那么问题变换为从a[1]开始选择整数,使得和为target。因此得到如下递归解法:
[java] view plain copy
  1. public List<List<Integer>> combinationSum(int[] candidates, int target) {  
  2.     Arrays.sort(candidates);  
  3.     List<List<Integer>> res=new ArrayList<List<Integer>>();  
  4.     ArrayList<Integer> r=new ArrayList<Integer>();  
  5.     solve(res,r,candidates,target,0);  
  6.     return res;  
  7. }  
  8. public void solve(List<List<Integer>> res,ArrayList<Integer> r,int[] candidates,int target,int index){  
  9.     if(target==0){  
  10.         ArrayList<Integer> clone=new ArrayList<Integer>(r);  
  11.         res.add(clone);  
  12.         return;  
  13.     }  
  14.     if(index>=candidates.length||target<candidates[index])return;   
  15.     //选择当前元素  
  16.     r.add(candidates[index]);  
  17.     solve(res, r, candidates, target-candidates[index], index);  
  18.     r.remove(r.size()-1);  
  19.     //不选择当前元素  
  20.     solve(res, r, candidates, target, index+1);  
  21.   
  22. }  

[java] view plain copy
  1. public List<List<Integer>> combinationSum2(int[] candidates, int target) {  
  2.     Arrays.sort(candidates);  
  3.     List<List<Integer>> res=new ArrayList<List<Integer>>();  
  4.     ArrayList<Integer> r=new ArrayList<Integer>();  
  5.     solve(res,r,candidates,target,0);  
  6.     return res;  
  7. }  
  8. public void solve(List<List<Integer>> res,ArrayList<Integer> r,int[] candidates,int target,int index){  
  9.     if(target==0){  
  10.         ArrayList<Integer> clone=new ArrayList<Integer>(r);  
  11.         res.add(clone);  
  12.         return;  
  13.     }  
  14.     if(index>=candidates.length||target<candidates[index])return;   
  15.     //选择当前元素  
  16.     r.add(candidates[index]);  
  17.     solve(res, r, candidates, target-candidates[index], index+1);  
  18.     r.remove(r.size()-1);  
  19.     //不选择当前元素  
  20.     int nextIndex=index+1;  
  21.     while(nextIndex<candidates.length&&candidates[nextIndex]==candidates[index])nextIndex++;  
  22.     solve(res, r, candidates, target, nextIndex);  
  23. }  


[java] view plain copy
  1. public List<List<Integer>> combinationSum3(int k, int n) {  
  2.     List<List<Integer>> res=new ArrayList<List<Integer>>();  
  3.     ArrayList<Integer> sub=new ArrayList<Integer>();  
  4.     solve(res,sub,k,n,1);  
  5.     return res;  
  6. }   
  7. private void solve(List<List<Integer>> res,ArrayList<Integer> sub,int k,int n,int start){  
  8.     if(sub.size()==k&&n==0){  
  9.         ArrayList<Integer> clone=new ArrayList<Integer>(sub);  
  10.         res.add(clone);  
  11.         return;  
  12.     }  
  13.     if(n<0||start==10||(sub.size()==k&&n!=0)){  
  14.         return;  
  15.     }  
  16.     //选择当前元素  
  17.     sub.add(start);  
  18.     solve(res,sub,k,n-start,start+1);  
  19.     sub.remove(sub.size()-1);   
  20.     //不选择当前元素  
  21.     solve(res,sub,k,n,start+1);  
  22. }  



分析
对于合法的括号表达式,从左边往右边看时,每时每刻左括号的个数大于等于右括号的个数。
我们可以将问题看成是对于长为2 n的字符串,从第一个位置开始我们选择'('或者')',同时要保证左括号个数永远大于右边括号的个数,并且最终左括号和右括号的个数都等于n。当我们做完所有的选择就得到了合法的括号表达式。
因此得到如下递归解法:

[java] view plain copy
  1. public List<String> generateParenthesis(int n) {  
  2.     List<String> res=new ArrayList<String>();  
  3.     if(n==0){  
  4.         res.add("");  
  5.         return res;  
  6.     }  
  7.     StringBuilder r=new StringBuilder();  
  8.     solve(n,0,0,res,r);  
  9.     return res;  
  10. }  
  11. private void solve(int n,int left,int right,List<String> res,StringBuilder r){  
  12.     if(r.length()==2*n){  
  13.         System.out.println(r.toString());  
  14.         res.add(r.toString());  
  15.         return;  
  16.     }   
  17.     if(left<right)return;  
  18.     if(left<n){  
  19.         //添加左边括号  
  20.         r.append("(");  
  21.         solve(n,left+1,right,res,r);  
  22.         r.setLength(r.length() - 1);  
  23.     }   
  24.     //添加右边括号  
  25.     r.append(")");  
  26.     solve(n,left,right+1,res,r);  
  27.     r.setLength(r.length() - 1);   
  28. }  

分析
采用分期摊还的方法。
[java] view plain copy
  1. public List<List<Integer>> subsets(int[] nums) {  
  2.     List<List<Integer>> res=new ArrayList<List<Integer>>();   
  3.     res.add(new ArrayList<Integer>());   
  4.     for(int i=0;i<nums.length;i++){  
  5.         List<List<Integer>> t=new ArrayList<List<Integer>>();   
  6.         for(List<Integer> r:res){   
  7.             t.add(r);  
  8.             ArrayList<Integer> clone=new ArrayList<Integer>(r);  
  9.             clone.add(nums[i]);  
  10.             t.add(clone);  
  11.         }  
  12.         res=t;  
  13.     }  
  14.     return res;  
  15. }  

分析
因为元素可以重复,并且需要过滤重复的集合,我们需要对生成子集的过程进行进一步的控制。我们将问题理解为,从a[0]开始求所有的子集,该问题可以分为两个问题1、选择a[0],从a[1]开始求所有子集并对每个子集加上a[0];2从a[1]开始求所有的子集。因此得到如下递归解法。
注:由于需要消除重复子集,因此我们在选择当前元素a[i]时,如果a[i-1]等于a[i]且我们没有选择,我们就必定不能选择a[i]。
[java] view plain copy
  1. public List<List<Integer>> subsetsWithDup(int[] nums) {  
  2.     Arrays.sort(nums);  
  3.     boolean[] used=new boolean[nums.length];  
  4.     List<List<Integer>> res=new ArrayList<List<Integer>>();   
  5.     ArrayList<Integer> sub=new ArrayList<Integer>();  
  6.     solve(res,0,sub,nums,used);   
  7.     return res;  
  8. }  
  9. private void solve(List<List<Integer>> res,int start,ArrayList<Integer> sub,int[] nums,boolean[] used){  
  10.     if(start==nums.length){  
  11.         ArrayList<Integer> clone=new ArrayList<Integer>(sub);  
  12.         res.add(clone);  
  13.         return;  
  14.     }  
  15.     //选择当前元素  
  16.     if(start>0&&nums[start]==nums[start-1]&&!used[start-1]){  
  17.         //do nothing  
  18.     }else{  
  19.         used[start]=true;  
  20.         sub.add(nums[start]);  
  21.         solve(res,start+1,sub,nums,used);  
  22.         sub.remove(sub.size()-1);  
  23.         used[start]=false;  
  24.     }  
  25.     //不选择当前元素  
  26.     solve(res,start+1,sub,nums,used);  
  27. }  
分析
我们从每一个位置开始回溯,并标记元素是否已经使用过。
[java] view plain copy
  1.   public boolean exist(char[][] board, String word) {  
  2.     boolean used[][]=new boolean[board.length][board[0].length];  
  3.     for(int i=0;i<board.length;i++){  
  4.         for(int j=0;j<board[0].length;j++){  
  5.             if(solve(board,word,i,j,used,0))   
  6.                 return true;  
  7.         }  
  8.     }  
  9.       return false;  
  10.   }  
  11.   public boolean solve(char[][] board,String word,int row,int col,boolean[][] used,int start){  
  12.     if(start==word.length()){  
  13.         return true;  
  14.     }  
  15.     if(row<0||col<0||row==board.length||col==board[0].length){  
  16.         return false;  
  17.     }    
  18.     if(board[row][col]!=word.charAt(start)||used[row][col]){  
  19.         return false;  
  20.     }  
  21.     if(used[row][col]){  
  22.         return false;  
  23.     }  
  24.     used[row][col]=true;  
  25. boolean mark=false;  
  26. mark=mark||solve(board,word,row+1,col,used,start+1);  
  27. mark=mark||solve(board,word,row-1,col,used,start+1);  
  28. mark=mark||solve(board,word,row,col+1,used,start+1);  
  29. mark=mark||solve(board,word,row,col-1,used,start+1);  
  30. used[row][col]=false;  
  31. return mark;  
  32.   }  

思考:如果我们有很多的单词需要查找时,如何避免重复的搜索过程呢?我们可以先建立trie树(单词查找树),然后对trie中的不同路径进行搜索。见leetcode 212 Word Search II


分析
对于每一个字符,我们首先找到以该字符为首的回文字符串,然后我们依次其中的回文传,并递归求解选择后的问题。
[java] view plain copy
  1. public List<List<String>> partition(String s) {  
  2.     List<List<String>> res=new ArrayList<List<String>>();  
  3.     List<String> sub=new ArrayList<String>();  
  4.     solve(res,sub,s,0);  
  5.     return res;  
  6. }  
  7. private void solve(List<List<String>> res,List<String> sub,String s,int start){  
  8.     if(start==s.length()){  
  9.         List<String> clone=new ArrayList<String>(sub);  
  10.         res.add(clone);  
  11.         return;  
  12.     }  
  13.     List<Integer> ends=new ArrayList<Integer>();  
  14.     for(int i=start;i<s.length();i++){  
  15.         if(isPalindrome(s,start,i)){  
  16.             ends.add(i);  
  17.         }  
  18.     }  
  19.     for(int end:ends){  
  20.         sub.add(s.substring(start, end+1));  
  21.         solve(res,sub,s,end+1);  
  22.         sub.remove(sub.size()-1);  
  23.     }  
  24. }  
  25. private boolean isPalindrome(String s,int start,int end){  
  26.     while(start<=end){  
  27.         if(s.charAt(start)!=s.charAt(end)){  
  28.             return false;  
  29.         }   
  30.         start++;end--;  
  31.     }  
  32.     return true;  
  33. }  

分析
对于每个待填的空位我们可以尝试1-9所有可能性,如果解决了问题就直接结束。如果所有尝试都没能解决问题,就需要调整之前已经确认的空位,回溯到调整上一个空位的值。因此,此处调用子问题时必须返回是否能够解决问题的转态,以便回溯。

[java] view plain copy
  1. public void solveSudoku(char[][] board) {  
  2.     ArrayList<ArrayList<Integer>> emptyLocations=  
  3.             new ArrayList<ArrayList<Integer>>();  
  4.     for(int row=0;row<9;row++){  
  5.         for(int col=0;col<9;col++){  
  6.             if(board[row][col]=='.'){  
  7.                 ArrayList<Integer> location=new ArrayList<Integer>();  
  8.                 location.add(row);location.add(col);  
  9.                 emptyLocations.add(location);  
  10.             }  
  11.         }  
  12.     }  
  13.     solve(board,0,emptyLocations);  
  14. }  
  15. private boolean solve(char[][] board,int index,ArrayList<ArrayList<Integer>> emptyLocations){  
  16.     if(index==emptyLocations.size()){  
  17.         return true;  
  18.     }  
  19.     ArrayList<Integer> location=emptyLocations.get(index);  
  20.     int row=location.get(0),col=location.get(1);  
  21.     for(char c='1';c<='9';c++){   
  22.         if(isValid(board,row,col,c)){  
  23.             board[row][col]=c;  
  24.             if(solve(board,index+1,emptyLocations)){  
  25.                 return true;  
  26.             }else{  
  27.                 board[row][col]='.';  
  28.             }  
  29.         }  
  30.     }  
  31.     return false;  
  32. }  
  33. public boolean isValid(char[][] board,int row,int col,char c){  
  34.     //验证行  
  35.     for(int i=0;i<9;i++){  
  36.         if(board[row][i]==c)  
  37.             return false;  
  38.     }  
  39.     //验证列  
  40.     for(int i=0;i<9;i++){  
  41.         if(board[i][col]==c)  
  42.             return false;  
  43.     }  
  44.     //验证3*3格子  
  45.     for(int i=(row/3)*3;i<(row/3)*3+3;i++){  
  46.         for(int j=(col/3)*3;j<(col/3)*3+3;j++){  
  47.             if(board[i][j]==c)  
  48.                 return false;  
  49.         }  
  50.     }  
  51.     return true;  
  52. }  


分析
我们知道N皇后问题通常采用回溯法求解。
方案一
对于每行每个位置进行尝试,并判断是否合法。
[java] view plain copy
  1. public List<List<String>> solveNQueens(int n) {   
  2.     ArrayList<Integer> locations=new ArrayList<Integer>();  
  3.     List<List<String>> res=new ArrayList<List<String>>();  
  4.     solve(res,locations,n);  
  5.     return res;  
  6. }  
  7. private void solve(List<List<String>> res,ArrayList<Integer> locations,int n){  
  8.     if(n==locations.size()){  
  9.         addRes(res,locations);  
  10.         return;  
  11.     }  
  12.     for(int i=0;i<n;i++){  
  13.         if(isValid(locations,i)){  
  14.             locations.add(i);  
  15.             solve(res,locations,n);  
  16.             locations.remove(locations.size()-1);  
  17.         }  
  18.     }   
  19. }  
  20. private boolean isValid(ArrayList<Integer> locations,int location){  
  21.     for(int i=0;i<locations.size();i++){  
  22.         if(location-locations.get(i)==locations.size()-i||  
  23.                 location-locations.get(i)==i-locations.size())  
  24.             return false;  
  25.         if(location==locations.get(i))  
  26.             return false;  
  27.     }  
  28. turn true;  
  29. }  
  30. private void addRes(List<List<String>> res,ArrayList<Integer> locations){  
  31.     List<String> r=new ArrayList<String>();  
  32.     for(int i=0;i<locations.size();i++){  
  33.         StringBuilder builder=new StringBuilder();  
  34.         for(int j=0;j<locations.size();j++){  
  35.             if(locations.get(i)==j){  
  36.                 builder.append("Q");  
  37.             }else{  
  38.                 builder.append(".");  
  39.             }  
  40.         }  
  41.         r.add(builder.toString());  
  42.     }  
  43.     res.add(r);  
  44. }  
方案二
我们将每行中皇后的位置用1-N表示,共N行。这1-N的任意排列,即可满足任意两个皇后不在同行或同列,我们只需要对产生的全排列进行验证即可。
[java] view plain copy
  1.   public List<List<String>> solveNQueens(int n) {  
  2.     int[] locations=new int[n+1];  
  3.     for(int i=1;i<=n;i++){  
  4.         locations[i]=i;  
  5.     }  
  6.     List<List<String>> res=new ArrayList<List<String>>();  
  7.     solve(res,locations,1);  
  8.     return res;  
  9.   }  
  10.   private void solve(List<List<String>> res,int[] locations,int index){  
  11.     if(index==locations.length){  
  12.         addRes(res,locations);  
  13.         return;  
  14.     }  
  15.     for(int i=index;i<=locations.length-1;i++){  
  16.         if(isValid(locations,index,i)){  
  17.             int t=locations[index];locations[index]=locations[i];locations[i]=t;  
  18.             solve(res,locations,index+1);  
  19.             t=locations[index];locations[index]=locations[i];locations[i]=t;  
  20.         }  
  21.     }  
  22.   }  
  23.   private boolean isValid(int[] locations,int index,int change){  
  24. for(int i=1;i<index;i++){  
  25.     if(locations[i]-locations[change]==index-i||  
  26.             locations[i]-locations[change]==i-index)  
  27.         return false;  
  28. }  
  29. return true;  
  30.   }  
  31.   private void addRes(List<List<String>> res,int[] locations){  
  32.     List<String> r=new ArrayList<String>();  
  33.     for(int i=1;i<=locations.length-1;i++){  
  34.         StringBuilder builder=new StringBuilder();  
  35.         for(int j=1;j<=locations.length-1;j++){  
  36.             if(locations[i]==j){  
  37.                 builder.append("Q");  
  38.             }else{  
  39.                 builder.append(".");  
  40.             }  
  41.         }  
  42.         r.add(builder.toString());  
  43.     }  
  44.     res.add(r);  
  45.   }  

注:在统计结果数量时,由于Java本身都是按值传递参数的(对于对象传递的是地址值),因此我们不用用int类型统计结果数量,同时由于Integer是不可变的,因此也不能使用Integer。这里我采用Integer容器来统计数量,此外还可以利用AtomicInteger原子整型或自定义引用类型来进行统计,也可以在方法调用中返回结果数量。如有更好的方法忘指教,谢谢!!

[java] view plain copy
  1.   public int totalNQueens(int n) {   
  2.     ArrayList<Integer> locations=new ArrayList<Integer>();    
  3.     Stack<Integer> count=new Stack<Integer>();  
  4.     count.add(new Integer(0));  
  5.     solve(locations,n,count);  
  6.     return count.get(0);  
  7.   }  
  8.   private void solve(ArrayList<Integer> locations,int n,Stack<Integer> count){  
  9.     if(n==locations.size()){  
  10.         count.push(new Integer(count.pop()+1));  
  11.         return;  
  12.     }  
  13.     for(int i=0;i<n;i++){  
  14.         if(isValid(locations,i)){  
  15.             locations.add(i);  
  16.             solve(locations,n,count);  
  17.             locations.remove(locations.size()-1);  
  18.         }  
  19.     }   
  20.   }  
  21.   private boolean isValid(ArrayList<Integer> locations,int location){  
  22.     for(int i=0;i<locations.size();i++){  
  23.         if(location-locations.get(i)==locations.size()-i||  
  24.                 location-locations.get(i)==i-locations.size())  
  25.             return false;  
  26.         if(location==locations.get(i))  
  27.             return false;  
  28.     }  
  29. return true;  
  30.   }  
原创粉丝点击