leetcode做题总结,回溯法(N-Queens, N-QueensII,Combination SumI&II,wordbreak II, SubsetsI&II)

来源:互联网 发布:网络捕鱼代理赚钱吗 编辑:程序博客网 时间:2024/05/18 00:02

最近打算对回溯法做一总结,回溯法的思路简单来说就是去试,把问题的可能解变成一棵决策树,然后用递归向下探路,如果走不通就向上回溯,中间可以用条件判断进行剪枝,避免不必要的历遍。


首先是回溯法最经典的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;    }}




下面是wordbreak II, 由于这道题是wordbreak的延伸,wb的解法是DP,但是backtrace也可以解,于是我用两种都做了,可是都超时。。后来我看了一下别人的解,发现问题在于在做之前需要用wordbreak的DP判断一下有没有解然后再做。。。通过这道题还学习到了ArrayList数组的使用,声明的方法是

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;    }}









0 0
原创粉丝点击