LeetCode39 组合总和

问题描述:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
例:输入: candidates = [2,3,6,7], target = 7。所求解集为:[ [7], [2,2,3] ]
注:

  • candidates 中的数字可以无限制重复被选取
  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合

思路

  • 递归回溯

通过递归回溯找到所有满足的组合。
图片来源于leetcode用户liweiwei1419
(图片来源于leetcode用户liweiwei1419)
时间复杂度:o(n2)
空间复杂度:o(n)

  • 动态规划

此问题可以看成动态规划问题,对小于target的元素先进行寻找

//java
//遍历回溯
class Solution {
    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        int len = candidates.length;
        if(len == 0)    return list;//数组长度为0,直接返回空列表
        Arrays.sort(candidates);//排序去重,不能选比前面选择还要小的元素
        findTargetSum(candidates, 0, target, new Stack<>());//以target作为回溯根节点进行回溯递归
        return list;
    }
    public void findTargetSum(int[] candidates, int start, int residue, Stack<Integer> temp){
        if(residue == 0){//找到可满足序列
            list.add(new ArrayList<>(temp));//将栈中元素加入列表
        }else if(residue > 0){//继续回溯
            for(int i = start; i < candidates.length && residue - candidates[i] >= 0; ++i){//从start开始向后进行回溯寻找,中间判断将减到<0的情况剔除
                temp.push(candidates[i]);//回溯元素入栈,作为可能满足的数字序列
                findTargetSum(candidates, i, residue - candidates[i], temp);//由于单个数字可取多次,对当前字符回溯
                temp.pop();//出栈
            }
        }
    } 
}

//动态规划
class Solution {
    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        int len = candidates.length;
        if(len == 0)    return list;
        Arrays.sort(candidates);
        Map<Integer, Set<List<Integer>>> imap = new HashMap();//值设置为集合,保证后面加入元素时不重复
        for(int i = 1; i <= target; ++i){//从1到target开始每个建立可满足集合
            imap.put(i, new HashSet<>());//初始化各元素可满足集合
            for(int j = 0; j < len && candidates[j] <= target; j++){//遍历数组,对每个小于i的元素进行减值判断
                if(i == candidates[j]){//剪完恰好为0,填入i对应的集合
                    imap.get(i).add(new ArrayList<>(Arrays.asList(i)));
                }else if(i > candidates[j]){
                    int key = i - candidates[j];
                    for(Iterator iterator = imap.get(key).iterator(); iterator.hasNext();){//关键部分,体现了动态规划的特点,对于减完的元素其满足情况对应于该较小值的可满足集合,由于在之前已经记录,仅将减去的candidates[j]值补充到原来的集合序列后面
                        List tempList = new ArrayList<>((List)iterator.next()); 
                        tempList.add(candidates[j]);//补充元素
                        Collections.sort(tempList);//排序保证不重复
                        imap.get(i).add(tempList);//集合的add方法会先判断元素是否已经存在在集合中,若存在将不会加入进去
                    }
                }else
                	break;
            }
        }
        list.addAll(imap.get(target));//获取target的满足集合
        return list;
    }
}

扩展:把题中条件改为数组中每个数字只能使用一次,并且数组中可能有重复数字。
思路:解决思路同之前一样,但由于每个数字只能取一次,在递归回溯时需要对下一数字进行回溯,同时,由于有重复字符,在回溯时需要跳过相同字符的回溯。
图片来源于leetcode用户liweiwei1419
注:由于原题中的动态规划思想是利用了子结构的性质,这里由于数字不能重复取,且存在重复元素,导致无法从子结构推出当前结构的组成(子结构中所取元素可能会与当前重复),所以上述第二种方法不适用。

class Solution {
    List<List<Integer>> list = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        if(candidates == null || candidates.length == 0)    return list;
        Arrays.sort(candidates);
        findTargetSum(candidates, 0, target, new Stack<>());
        return list;
    }
    public void findTargetSum(int[] candidates, int start, int residue, Stack<Integer> temp){
        if(residue == 0){
            list.add(new ArrayList<>(temp));
        }else if(residue > 0){
            for(int i = start; i < candidates.length && residue >= candidates[i]; ++i){
                if(i == start || candidates[i] != candidates[i-1]){//改动之处:当前字符为回溯每一层首先所考虑字符或当前字符与上一字符不同,进行回溯(因为如果不为首考虑字符且与前面字符相同,对该字符的后续回溯会与前一相同字符的部分相同)
                    temp.push(candidates[i]);
                    findTargetSum(candidates, i+1, residue-candidates[i], temp);
                    temp.pop();
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leo木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值