力扣第40题 组合总和2

40. 组合总和 II

题目

这道题给我提供了一个新的思路。就是一旦在深度遍历的时候,出现了重复的问题,那么,可以将出现问题的源头当成一个整体来处理。

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

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

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

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

思路

一看到这个题,首先想到的就是 DFS。
        首先,将数组从小到大排序。定义一个list用于存储当前的路径,它是动态的。定义一个list<list<Integer>>,用来装所有可能的路径。
        然后进行DFS。当前选择下标为i的元素,那么下次选择就可以选下标为 i+1,i+2,...len-1 的元素。所有,DFS需要写在for循环中。
            并且,需要回溯。所有,将元素添加进列表、下一层dfs、将元素移出列表 这三部分写在循环当中。
            这个DFS的条件就是 targer - 每次选择的元素之和 是否是0;
​
但是需要解决的问题是: 解集不能包含重复的组合! 上述的DFS必然会造成有重复解,因为candidates中可以存在重复元素。
【重点】 如果candidates中的数都是不重复的,上面的思路就没有问题。对于 [1,2,2,2,5],target = 5 来说,当选择了第0号元素和第1号元素,那么接下来可以选第2号元素或第3号元素,这就出现了重复解。 
有没有一种可能,我们将把这些重复的元素当成一个整体来处理呢?
    我们统计好元素的值与出现的次数,然后按照元素的值的大小排列得到一个二维数组:
            [
                [1,1],  元素1
                [2,3],  元素2
                [5,1]   元素3
            ]
    我们在进行DFS时,将相同的元素当成一个整体来处理。当我们面对每一个元素,我们都可以选择是取几个(不超过元素本身数量)这样的元素加入到列表。
    ①对于元素1,选择1个。left = 5 - 1*1 = 4
        对于元素2,选择1个。left = 4 - 2*1 = 1
            对于元素3,不能进行选择
        对于元素2,选择2个。left = 4 - 2*2 = 0 --> 将 [1,2,2]加入到结果集
    ②对于元素2,选择1个。 left = 5 - 2 = 3
        对于元素3,不能进行选择。
     对于元素2,选择2个。left = 5 - 2*2 = 1
        对于元素3,不能进行选择。
    ③对于元素3,选择1个。 left = 5 - 5 * 1 = 0  --> 将[5]加入结果集
    
    也就是说,我们在DFS的时候有两层循环。外层循环是将 <元素值e> 作为遍历元素。
                                    内层循环,是将 i = [1..t] (该元素出现的次数 t) 作为遍历元素。
    每次进路径和出路径都同时进 i个e和出i个e 即可。
     

代码

class Solution {
    public List<List<Integer>> res = new ArrayList<>();
    public List<Integer> path = new ArrayList<>();
    public int[][] num_times;
    public int len;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        //统计数组中各个数出现的次数
        Map<Integer,Integer> map = new TreeMap<>();
        for (int i : candidates){
            map.put(i,map.getOrDefault(i,0) + 1);
        }
        //将map中的键值对转移到数组中
        num_times = new int[map.size()][2];
        len = map.size();
        Set<Integer> set = map.keySet();
        Iterator<Integer> it = set.iterator();
        int i = 0;
        while (it.hasNext()){
            Integer key = it.next();
            num_times[i][0] = key;
            num_times[i][1] = map.get(key);
            i++;

        }
        //dfs
        dfs(0,target);
        return res;

    }
    public void dfs(int cur,int left){
        if(left < 0){
            return;
        }
        if (left == 0){
            ArrayList<Integer> newPath = new ArrayList<>(path);
            res.add(newPath);
            return;
        }
        //下一个取到的可能是后面的任意一个元素
        for (int i = cur; i < len; i++){
            
            //当前元素取j个
            for(int j = 1; j <= num_times[i][1]; j++){
                //取不了这么多,就break,也就是说跳过当前键值对
                if(left - num_times[i][0] * j < 0){
                    break;
                }
                //取当前的值
                for (int k = 0; k < j; k++) path.add(num_times[i][0]);
                dfs(i + 1,left - num_times[i][0] * j);
                //回溯
                for (int k = 0; k < j; k++) path.remove(path.size() - 1);
            }

        }
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值