最近打算对回溯法做一总结,回溯法的思路简单来说就是去试,把问题的可能解变成一棵决策树,然后用递归向下探路,如果走不通就向上回溯,中间可以用条件判断进行剪枝,避免不必要的历遍。
首先是回溯法最经典的N皇后问题,思路并不麻烦,从第一行开始依次设一个点为Q,然后向第二行前进,历遍每一个点,如果某个点可行就继续前进。
public class Solution {
//checking if a specific position in row row is valid.
private boolean check(int row, int[] queenpositions){
for(int i=0;i<row;i++){
if(queenpositions[row]==queenpositions[i] || Math.abs(queenpositions[row]-queenpositions[i])==row-i)
return false;
}
return true;
}
private void queen(int n, int row, int[] queenpositions, ArrayList<String[]> l){
if(row == n){
String[] res = new String[n];
for(int i=0;i<n;i++){
String strRow="";
for(int j=0;j<n;j++){
if(queenpositions[i]==j)
strRow+="Q";
else
strRow+=".";
}
res[i] = strRow;
}
l.add(res);
}else{
for(int i=0;i<n;i++){
queenpositions[row] = i;
if(check(row,queenpositions)){
queen(n,row+1,queenpositions,l);
}
}
}
}
public List<String[]> solveNQueens(int n) {
ArrayList<String[]> l = new ArrayList<String[]>();
//Storing the queen position of each row.
int[] queenpositions = new int[n];
queen(n,0,queenpositions, l);
return l;
}
}
之后是N皇后II,就是计算N皇后有几种解法
public class Solution {
int sum=0;
//checking if a specific position in row row is valid.
private boolean check(int row, int[] queenpositions){
for(int i=0;i<row;i++){
if(queenpositions[row]==queenpositions[i] || Math.abs(queenpositions[row]-queenpositions[i])==row-i)
return false;
}
return true;
}
private void queen(int n, int row, int[] queenpositions){
if(row == n)
sum++;
else{
for(int i=0;i<n;i++){
queenpositions[row] = i;
if(check(row,queenpositions)){
queen(n,row+1,queenpositions);
}
}
}
}
public int totalNQueens(int n) {
int[] queenpositions = new int[n];
queen(n,0,queenpositions);
return sum;
}
}
下一题是Letter Combinations of a Phone Number . 典型的backtrace,没什么可说的。
public class Solution {
private void phone(int k, int n, String[] input,char[] output,ArrayList<String> l){
if(k==n){
String res="";
//The first element of output is ""
for(int i=1;i<n;i++){
res+=output[i];
}
l.add(res);
}else{
if(Integer.parseInt(input[k])>0&&Integer.parseInt(input[k])<7){
for(int i=97+3*(Integer.parseInt(input[k])-2);i<97+3*(Integer.parseInt(input[k])-1);i++){
output[k]=(char)i;
phone(k+1, n, input,output, l);
}
}else if(Integer.parseInt(input[k])==7){
for(int i=112;i<116;i++){
output[k]=(char)i;
phone(k+1, n, input,output, l);
}
}else if(Integer.parseInt(input[k])==9){
for(int i=119;i<123;i++){
output[k]=(char)i;
phone(k+1, n, input,output, l);
}
}else{
for(int i=116;i<119;i++){
output[k]=(char)i;
phone(k+1, n, input,output, l);
}
}
}
}
public List<String> letterCombinations(String digits) {
ArrayList<String> l = new ArrayList<String>();
//if string is empty, return l;
if(digits.length()<1){
String res="";
l.add(res);
return l;
}
//important!! The first element of input is "". Thus we should start from index=1
String[] input = digits.split("");
char[] output=new char[input.length];
phone(1,input.length,input,output,l);
return l;
}
}
在这道题的时候,学到了点新的知识,不是对字符串用split("")来分,返回的数组第一个元素为空。还有就是容器和数组一样,传入函数即可直接修改,函数不用返回容器,除非是在函数内new的。
下一道题是Combination Sum,这道题也是同样的思路,要提一下的是如果往一个listA里添加另一个ListB,这是最好复制一个新的list加进去,否则如果外部对这个listB修改,会导致A里面的元素改变!新建的方法为new ArrayList(oldlist);
public class Solution {
private void combi(int left, int sum,int[] candidates, ArrayList<Integer> res,ArrayList<List<Integer>> l,int target){
if(sum==target){
//if we need a new ArrayList to be added to l. Because if we change the res2 which will also be changed in l.
ArrayList<Integer> res2= new ArrayList<Integer>(res);
l.add(res2);
}else if(sum<target){
for(int i=left;i<candidates.length;i++){
res.add(candidates[i]);
sum+=candidates[i];
combi(i,sum,candidates,res,l,target);
sum-=candidates[i];
res.remove(res.size()-1);
}
}
}
public List<List<Integer>> combinationSum(int[] candidates, int target) {
ArrayList<List<Integer>> l = new ArrayList<List<Integer>>();
if(candidates.length==0)
return l;
Arrays.sort(candidates);
ArrayList<Integer> res= new ArrayList<Integer>();
combi(0,0,candidates,res,l,target);
return l;
}
}
下一道题是Combination Sum II,这道题和迁移到差不多,只是在递归的时候修改开始位置防止重复,还有一个问题是listA里的ListB有可能会出项重复,这样要在加入是用contains进行判断。其实按理说上一题也会有这样的问题,只是testcase里没有所以没检测出来。
public class Solution {
private void combi(int left, int sum,int[] candidates, ArrayList<Integer> res,ArrayList<List<Integer>> l,int target){
if(sum==target){
if(l.contains(res));
else{
ArrayList<Integer> res2= new ArrayList<Integer>(res);
l.add(res2);
}
}else if(sum<target){
for(int i=left;i<candidates.length;i++){
res.add(candidates[i]);
sum+=candidates[i];
combi(i+1,sum,candidates,res,l,target);
sum-=candidates[i];
res.remove(res.size()-1);
}
}
}
public List<List<Integer>> combinationSum2(int[] num, int target) {
ArrayList<List<Integer>> l = new ArrayList<List<Integer>>();
if(num.length==0)
return l;
Arrays.sort(num);
ArrayList<Integer> res= new ArrayList<Integer>();
combi(0,0,num,res,l,target);
return l;
}
}
Update 2015/08/28:上面的思路正确但是写法繁琐,对于需要递归累加求target得题目,相较于上面的使用sum记录,更好的是对减小target, 减到零就说明相等了。
public class Solution {
/**
* @param candidates: A list of integers
* @param target:An integer
* @return: A list of lists of integers
*/
public void compute(
ArrayList<List<Integer>>res,
ArrayList<Integer> tmp,
int[] num,
int s,
int target){
if (target == 0){
if (!res.contains(tmp))
res.add(new ArrayList<Integer>(tmp));
}
if (target < 0)
return;
for (int i = s; i < num.length; i++){
tmp.add(num[i]);
compute(res, tmp, num, i, target - num[i]);
tmp.remove(tmp.size() - 1);
}
}
public List<List<Integer>> combinationSum(int[] candidates, int target) {
// write your code here
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
ArrayList<Integer> tmp = new ArrayList<Integer>();
Arrays.sort(candidates);
compute(res, tmp, candidates, 0, target);
return res;
}
}
public class Solution {
/**
* @param num: Given the candidate numbers
* @param target: Given the target number
* @return: All the combinations that sum to target
*/
public void compute(
ArrayList<List<Integer>>res,
ArrayList<Integer> tmp,
int[] num,
int s,
int target){
if (target == 0){
if (!res.contains(tmp))
res.add(new ArrayList<Integer>(tmp));
}
if (target < 0)
return;
for (int i = s; i < num.length; i++){
tmp.add(num[i]);
compute(res, tmp, num, i + 1, target - num[i]);
tmp.remove(tmp.size() - 1);
}
}
public List<List<Integer>> combinationSum2(int[] num, int target) {
// write your code here
ArrayList<List<Integer>> res = new ArrayList<List<Integer>>();
ArrayList<Integer> tmp = new ArrayList<Integer>();
Arrays.sort(num);
compute(res, tmp, num, 0, target);
return res;
}
}
ArrayList<ArrayList<String>>[] sa = new ArrayList[2];
sa[0] = new ArrayList<ArrayList<String>>();
这道题的解法是
public class Solution {
private void getword(int start, String s, String res,ArrayList<String> l,Set<String> dict){
if(start==s.length()){
l.add(res.substring(1));
}else{
for(int i=start;i<s.length();i++){
if(dict.contains(s.substring(start,i+1))){
String newres=res+" "+s.substring(start,i+1);
getword(i+1,s,newres,l,dict);
}
}
}
}
public List<String> wordBreak(String s, Set<String> dict) {
ArrayList<String> l = new ArrayList();
if(s.length()==0)
return l;
String res="";
int n = s.length();
boolean[] dp = new boolean[n+1];
dp[0] = true;
for (int i=1; i<=n; i++) {
if (dict.contains(s.substring(0, i))) {
dp[i] = true;
continue;
}
for (int j=0; j<i; j++) {
if (dp[j] && dict.contains(s.substring(j, i))) {
dp[i] = true;
}
}
}
if (dp[n] == false) return l;
getword(0,s,res,l,dict);
return l;
}
}
下面是Subsets, 这道题不知道为什么会被归到动态规划里去,但是我用回溯法搞定了。题目是从一个set里提取出来子Set,然后从单元素到多元素向下递归。
public class Solution {
private void getsub(int left, int[] S,ArrayList<Integer> res, ArrayList<List<Integer>> l){
if(left<S.length){
for(int i=left; i<S.length; i++){
res.add(S[i]);
//never forget add a new ArrayList to outter ArrayList!
ArrayList<Integer> restmp = new ArrayList<Integer>(res);
l.add(restmp);
getsub(i+1, S, res, l);
//never forget remove the element for backtracing.
res.remove(res.size()-1);
}
}
}
public List<List<Integer>> subsets(int[] S) {
ArrayList<List<Integer>> l = new ArrayList<List<Integer>>();
if(S.length==0)
return l;
ArrayList<Integer> res = new ArrayList<Integer>();
//never forget sort!
Arrays.sort(S);
getsub(0, S, res, l);
l.add(new ArrayList<Integer>());
return l;
}
}
Subsets II 这道题和上道题思路一样,只是由于根Set里可能有重复元素,这种问题之前遇到过好多次,方法是在加入外层list前用contains判断一下即可。
public class Solution {
private void getsub(int left, int[] S,ArrayList<Integer> res, ArrayList<List<Integer>> l){
if(left<S.length){
for(int i=left; i<S.length; i++){
res.add(S[i]);
ArrayList<Integer> restmp = new ArrayList<Integer>(res);
//for array with duplicate element, all we need to do is check if it already exits in outter list
if(!l.contains(restmp))
l.add(restmp);
getsub(i+1, S, res, l);
res.remove(res.size()-1);
}
}
}
public List<List<Integer>> subsetsWithDup(int[] num) {
ArrayList<List<Integer>> l = new ArrayList<List<Integer>>();
if(num.length==0)
return l;
ArrayList<Integer> res = new ArrayList<Integer>();
Arrays.sort(num);
getsub(0, num, res, l);
l.add(new ArrayList<Integer>());
return l;
}
}