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

本文深入探讨回溯法在N皇后问题、电话号码字母组合、组合求和等问题中的应用,提供详细的算法实现及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


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









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值