回溯+剪枝+去重 LeetCode : 39组合总和;LeetCode 40组合总和II; LeetCode 46 全排列;LeetCode 47全排列 II

数组取元素搞排列:递归、回溯、剪枝
想象一棵树,树中不同层次、不同节点即代表了当前选择的不同状态。
dfs遍历该树,根据题目条件进行剪枝回溯,找出所有满足结果即可。

取全部元素:使用标记数组,每次都需要判断每个元素是否可用;
取部分元素:可按照顺序(数组下标)取满足条件的一部分数据,判断是否可用;

数组相关解集无重复问题:一般需要首先对数组进行排序,遍历数组时考虑nums[i]=nums[i+1]
这样某一区间元素相等的情况,大多都只需要对第一个或最后一个元素进行处理,而其他的跳过。

结果集无重复:想象树结构,相同元素,相同层次最多只能取一次,而不同层次则对重复性无影响。
在取第i个位置元素时(即树的第i层),相同元素只能取一次。如[1,[2,2],[1]]中第2层只能取一次2,而[1,[2,1],2]可以取
39. 组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
思路:
	**无重复元素,无限制选取,结果不能重复**: candidates[i] 取? 不取? 取几次?
	回溯枚举所有可能情况(排列),中间剪枝优化
// 参数解释:candidates元素数组;pos指针 数组中<=pos位置元素可用;target目标值; 
//         now当前枚举列表;ll最终结果二维链表
    public void getList(int[] candidates,int pos,int target,List<Integer> now,List<List<Integer>> ll){
        if(target==0){
            // !!!这里需要复制新建一个链表,而不能直接传递now!!!
            ll.add(new ArrayList<Integer>(now)); 
            return ;
        }
        for(int i=0;i<=pos&&candidates[i]<=target;i++){
            now.add(candidates[i]);
            // 无限制选取(可多次选取),下次递归数组端点是当前位置i;若只能取一次,下次递归端点应该为i-1
            getList(candidates,i,target-candidates[i],now,ll);
            now.remove(now.size()-1);
        }
    }
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> ll=new ArrayList<List<Integer>>();
        //   !!!一维数字数组,先想排序!!!
        Arrays.sort(candidates);	
        getList(candidates,candidates.length-1,target,new ArrayList<Integer>(),ll);
        return ll;
    }
40. 组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。 
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[[1, 7], [1, 2, 5], [2, 6], [1, 1, 6]]
思路: candidates **有重复元素,不能重复选取,结果不能重复**
	() candidates中包含重复元素,而又要保证最终解集无重复: 
	考虑不能重复,每个数字是有限的,排序,只保留唯一元素并记录每个元素的出现次数。
	对每个可能结果,记录每个元素已出现次数后面尝试时值可以相等,但是不能超过出现次数      
	== 有点完全背包思想?
	public void getList(int[] nums,int[] cnts,int[] vis,int pos,int target,List<Integer> now,List<List<Integer>> ll){
        if(target==0){
            ll.add(new ArrayList<Integer>(now));
            return ;
        }
        for(int i=0;i<=pos&&nums[i]<=target&&vis[i]<cnts[i];i++){
            now.add(nums[i]);
            vis[i]+=1;
            getList(nums,cnts,vis,i,target-nums[i],now,ll);
            vis[i]-=1;
            now.remove(now.size()-1);
        }
    }
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> ll=new ArrayList<List<Integer>>();
        Arrays.sort(candidates);
        // 非重复元素数组,元素个数,元素出现次数
        int nums[]=new int[candidates.length];
        int cnts[]=new  int[candidates.length];
        int vis[]=new  int[candidates.length];
        int len=0;
        for(int i=0;i<candidates.length;i++){
            if(i==0){
                nums[len]=candidates[i];
                cnts[len++]+=1;
            }else{
                if(candidates[i]==nums[len-1]){
                    cnts[len-1]+=1;
                }else{
                    nums[len]=candidates[i];
                    cnts[len++]+=1;
                }
            }
        }
        getList(nums,cnts,vis,len-1,target,new ArrayList<Integer>(),ll);
        return ll;
    }
    () 结合树结构进行剪枝
    public void getList(int[] candidates,int pos,int target,List<Integer> now,List<List<Integer>> ll){
        if(target==0){
            ll.add(new ArrayList<Integer>(now)); 
            return ;
        }
        for(int i=0;i<=pos&&candidates[i]<=target;i++){
        // 想象树结构,[1,[2,2],1]是会产生重复的,而[1,[2],2]并没有重复。
        // 即对于树结构,在同一层次进行剪枝,同一层次中相同元素只处理最后一个或第一个,不同层次之间不用处理即可
            if(i<pos&&candidates[i]==candidates[i+1]){ 
                continue;
            }
            now.add(candidates[i]);
            getList(candidates,i-1,target-candidates[i],now,ll);
            now.remove(now.size()-1);
        }
    }
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> ll=new ArrayList<List<Integer>>();
        Arrays.sort(candidates);
        getList(candidates,candidates.length-1,target,new ArrayList<Integer>(),ll);
        return ll;
    }
46. 全排列
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
输入: [1,2,3]
输出:
[[1,2,3],[1,3,2], [2,1,3],[2,3,1],[3,1,2],[3,2,1]]
思路: 数组***无重复元素,结果不能重复,要用到所有元素***:递归+回溯+剪枝, 使用标记数组记录每个方案中每个元素是否使用
    public void dfs(int[] nums,int total,int cnt,int[] vis,List<List<Integer>> ll,List<Integer> l){
        if(cnt==total){
            ll.add(new ArrayList<>(l));
            return;
        }
        for(int i=0;i<total;i++){
            if(vis[i]!=0)	//当前方案中 第i位置元素已被使用
                continue;
            vis[i]=1;
            l.add(nums[i]);
            dfs(nums,total,cnt+1,vis,ll,l);
            l.remove(l.size()-1);
            vis[i]=0;
        }
    }
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> ll=new ArrayList<List<Integer>>();
        int[] vis=new int[nums.length];
        dfs(nums,nums.length,0,vis,ll,new ArrayList<>());
        return ll;
    }
47. 全排列 II
给定一个可包含重复数字的序列,返回所有不重复的全排列。
输入: [1,1,2]
输出:
[[1,1,2], [1,2,1], [2,1,1]]
思路:
	***有重复元素,结果不能重复,必须用到全部元素***:相较于上一个问题需要考虑排列重复问题,由前面的剪枝技巧,设置每一层相同元素只使用第一个或者最后一个即可。
	public void dfs(int[] nums,int total,int cnt,int[] vis,List<List<Integer>> ll,List<Integer> l){
        if(cnt==total){
            ll.add(new ArrayList<>(l));
            return;
        }
        for(int i=0;i<total;i++){
        	// 如果nums[i]=nums[i+1] 并且 i+1元素是可用的,只选择i+1
        	// 如 nums=[1,2,2,2,3] vis=[1,0,0,1,0] 则该层只遍历nums[2]=2与nums[4]=3两个元素 
            if((vis[i]!=0)||(i<total-1&&vis[i+1]==0&&nums[i]==nums[i+1]))
                continue;
            vis[i]=1;
            l.add(nums[i]);
            dfs(nums,total,cnt+1,vis,ll,l);
            l.remove(l.size()-1);
            vis[i]=0;
        }
    }
    public List<List<Integer>> permuteUnique(int[] nums) {
         List<List<Integer>> ll=new ArrayList<List<Integer>>();
        Arrays.sort(nums);
        int[] vis=new int[nums.length];
        dfs(nums,nums.length,0,vis,ll,new ArrayList<>());
        return ll;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值