对回溯算法的理解

回溯算法是纯暴力算法,虽然并不高效,但有些问题能求出来就好了。
比如组合,排列,分割,子集,棋盘,求树的路径等问题就适合回溯算法,一个问题,如果不确定有多少层循环,就可以使用回溯算法。
事实上回溯算法都可以抽形成树形结构,
结果中的每一个数据,都对应着一层递归。每层递归会把某一位数据可能出现的情况都遍历一次。
以组合问题为例,假设所给数组中没有重复数据。

  • 首先一个for循环遍历数组,循环里面放这递归,递归开始前先将当前数据入临时结果,递归函数下面把该数据从临时结果中去掉;
  • 下一次递归的for循环就从当前已经使用过的数据的下一个开始;第i层递归,代表的就是组合里面第i个数据的可能情况。
  • 最后弄一个结束条件,当开始下标为最后一个数据的时候,代表已经把数据全部遍历了,就将结果放入结果集。这样就可以把不知道有多少层循环的问题给暴力出来。

以leetcode----77为例:
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。

import java.util.*;
class Solution {
    ArrayList<Integer> list=new ArrayList<>();
    List<List<Integer>> lists=new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        combineDes(n,k,1);
        return lists;
    }
    private void combineDes(int n,int k,int startIndex){//因为是组合,没有顺序,所以有开始下标
        if(list.size()==k){//结束条件,并把符合情况的结果加入结果集
            ArrayList<Integer> temp=new ArrayList(list);
            lists.add(temp);
            return;
        }
        for(int i=startIndex;i<=n;i++){//横向遍历数组,第i次递归,就代表第i位数可能出现的情况
            list.add(i);//进入递归就把结果加进去
            combineDes(n,k,i+1);//进入递归,排列下一个数可能的情况。
            list.remove(list.size()-1);//递归完成之后就把结果移除
        }
    }
}

如果是排列问题,那么数据就有顺序,这样每层递归的循环都从下标0开始,并且需要增加一个used数组来查看数据是否被用过。
在循环内部,进入递归之前used[i]置为true,代表被使用,递归结束之后置为false,代表没有被使用。

如果数组中存在重复数据,结果集里面的结果不允许重复,那么在当前递归层里面不能重复选取同一个数据。比如在第一层递归里面,如果第一个数据是1,并且进入递归,第二个数据也是1,那么在该层递归里面,第二个1就不应该进入递归,不然就会出现两个1xxx的结果。(也就是arr[i]==arr[i-1]&&used[i-1]==false),used[i]等于false代表进入了递归又出来了。所以不能再进入相同数据的递归,就跳过,因为整个for循环选取的是第i个数据的可能情况,不能出现重复。不过同一个结果里面可以有重复数据,所以arr[i]==arr[i-1]&&used[i-1]==true的时候就可以。

以leetcode------40为例:
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

注意:解集不能包含重复的组合。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum-ii

class Solution {
    private List<Integer> list=new ArrayList<>();
    private List<List<Integer>> lists=new ArrayList<List<Integer>>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        if(candidates.length==0){
            return lists;
        }
        Arrays.sort(candidates);
        boolean[] used=new boolean[candidates.length];
        getPath(candidates,0,0,target,used);
        return lists;
    }
    private void getPath(int[] candidates,int startIndex,int sum,int target,boolean[] used){
        if(sum>target){//结束条件
            return;
        }
        if(sum==target){//结束条件
            List<Integer> newList=new ArrayList(list);
            lists.add(newList);
            return;
        }
        for(int i=startIndex;i<candidates.length&&(sum+candidates[i]<=target);i++){//
            if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){
                //如果前一个值和当前值相同,并且前一个值和当前值不在同一个组合里面,就需要跳过。
                //used[i-1]=true就代表前一个数据和当前选择的数据在同一个组合里面,前一个数据是上一层递归加入的。
                continue;
            }
            sum+=candidates[i];
            list.add(candidates[i]);
            used[i]=true;
            getPath(candidates,i+1,sum,target,used);
            used[i]=false;
            sum-=candidates[i];
            list.remove(list.size()-1);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值