Leetcode 回溯法总结

对于智商天花板略低的我来说,起初回溯法还是让我费了点功夫去理解内部流程,以防后面忘记了,现在记录一下吧。
首先上回溯法的一般框架:

int solution[MAX_DIMENSION];    //多维度解
void backtrack(int dimension)
{
    /*检验当前维度数组是否是一个解*/
    /* 临界判断,当前情况是否是一个解 */
    if ( solution[] is well-generated )
    {
        check and record solution;
        return;
    }

    /* 列举当前维度的所有取值的情况,并且进入到下一维度*/
    for ( x = each value of current dimension )
    {
        solution[dimension] = x;
        backtrack( dimension + 1 );
    }
}

直接上例子。
先来个简单的, 401. Binary Watch

    public List<String> readBinaryWatch(int num) {
        List<String> res = new ArrayList<String>();
        //表示10个led灯的位置
        int[] watch = new int[10];
        Trial(num, 0, watch, res);
        return res;
    }

    //n 表示当前还需要点亮几个led灯, a 当前从第几个位置开始点亮
    public static void Trial(int n,int a,int[] watch,List<String> times){
        //参照模板, 首先进行临界条件判断,也就是recursion的边界条件判断。当n == 0时表示一次搜索走到底了,可以得到一个结果了。
        if(n == 0){
            int hour = 0, min = 0;
            for(int i=0; i<4; i++){
                hour += watch[i] * Math.pow(2, 3-i);
            }
            for(int i=4; i<10; i++){
                min += watch[i] * Math.pow(2, 9-i);
            }
            String temp = Integer.toString(hour) + ":";
            if(min < 10){
                temp += "0";
            }
            temp += Integer.toString(min);
            times.add(temp);
            return;
        }
        //当前情况, 下一个点亮的位置可以在a~9当中选择,那就直接遍历
        for(; a<=9; a++){
            //点亮下一个led灯
            watch[a] = 1;
            //条件取舍,对于hour>11 和 min>59的情况舍去
            if(! (watch[0] == 1 && watch[1] == 1 || watch[4] == 1 && watch[5] == 1 && watch[6] == 1 && watch[7] == 1)){
                //继续深度搜索
                Trial(n-1, a+1, watch, times);
            }
            //a~9的情况全部搞定,换个a继续
            watch[a] = 0;
        }
        return;
    }

接着来看 39. Combination Sum


    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        //先对候选数字进行排序,避免结果重复
        Arrays.sort(candidates);
        List<List<Integer>> solutions = new ArrayList<>();
        backtrack(candidates, target, new ArrayList<Integer>(), solutions, 0);
        return solutions;
    }

    //其中remain表示还差多少能到target。chose是当前已经选择的数字的box。
    public static void backtrack(int[] candidates, int remain, List<Integer> chose, List<List<Integer>> solutions, int start){
        //如果chose内的和已经大于target, 考虑到候选数都是正数,没必要继续加了,直接回溯
        if(remain < 0) return;
        //如果chose内的和等于target, 那么这个chose就是我们需要的结果之一
        else if(remain == 0) solutions.add(new ArrayList<>(chose));
        else{
            //当前情况, 下一个候选数可以在start~candidates.length当中选择,那就直接遍历
            for(int i=start; i<candidates.length; i++){
                //选择下一个候选数
                chose.add(candidates[i]);
                //继续深度搜索,注意到这里是i而不是i+1,因为候选数允许被多次选择
                backtrack(candidates, remain - candidates[i], chose, solutions, i);
                //回溯,继续
                chose.remove(chose.size() - 1);
            }
        }
    }

下一个例子, 40. Combination Sum II
相对于上一个例子, 这里只是有两个改变而已。首先, 候选数集里有重复的数,所以我们需要去除重复的数。然后,因为候选集里的数只能被用一次,所以递归那儿的i要变成i+1。代码如下

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        List<List<Integer>> solutions = new ArrayList<>();
        backtrack(candidates, target, new ArrayList<Integer>(), solutions, 0);

        return solutions;
    }

    public static void backtrack(int[] candidates, int remain, List<Integer> chose, List<List<Integer>> solutions, int start){
        if(remain < 0) return;
        else if(remain == 0) solutions.add(new ArrayList<>(chose));
        else{
            for(int i=start; i<candidates.length; i++){
                if(i > start && candidates[i] == candidates[i-1]) continue;
                chose.add(candidates[i]);
                backtrack(candidates, remain - candidates[i], chose, solutions, i+1);
                chose.remove(chose.size() - 1);
            }
        }
    }

继续往下, 46. Permutations


    public List<List<Integer>> permute(int[] nums) {
        //套路如上,准备好答案的容器
        List<List<Integer>> solutions = new ArrayList<>();
        //占位符,待会儿会用到
        boolean[] use = new boolean[nums.length];
        backtrack(nums, use, nums.length, new ArrayList<Integer>(), solutions);
        return solutions;
    }

    //use表示哪个位置已经被用了,count表示还剩几个位置没有被选到1
    private static void backtrack(int[] nums, boolean[] use, int count, List<Integer> chose, List<List<Integer>> solutions){
        //if...else...熟悉的套路
        if(count == 0){
            solutions.add(new ArrayList(chose));
        }else{
            for(int i=0; i<nums.length; i++){
                if(use[i] == false){
                    chose.add(nums[i]);
                    use[i] = true;
                    backtrack(nums, use, count - 1, chose, solutions);
                    chose.remove(chose.size() - 1);
                    use[i] = false;
                }
            }
        }

    } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值