组合总和 II(Java)

给定一个数组 arr 和一个目标数 target ,找出 arr 中所有可以使数字和为 target 的组合。

arr 中的每个数字在每个组合中只能使用一次。
说明:
    所有数字(包括目标数)都是正整数。
    解集不能包含重复的组合。

示例 1:
输入: arr = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

示例 2:
输入: arr = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]


需要求出所有和为 target 的组合,并且每个数只能使用一次,因此可以使用递归 + 回溯的方法:
    用 combinationSumIIDFS(pos,rest) 表示递归的函数,其中 pos 表示当前递归到了数组 arr 中的第 pos 个数,而 rest 表示还需要选择和为 rest 的数放入列表作为一个组合;
    对于当前的第 pos 个数,有两种方法:选或者不选。如果选了这个数,那么调用 combinationSumIIDFS(pos+1,rest−arr[pos]) 进行递归,注意这里必须满足 rest≥arr[pos]。如果不选这个数,那么调用 combinationSumIIDFS(pos+1,rest) 进行递归;
    在某次递归开始前,如果 rest 的值为 0,说明找到了一个和为 target 的组合,将其放入答案中。每次调用递归函数前,如果选了那个数,就需要将其放入列表的末尾,该列表中存储了选的所有数。在回溯时,如果选了那个数,就要将其从列表的末尾删除。
上述算法就是一个标准的递归 + 回溯算法,但是它并不适用于本问题求解。这是因为题目描述中规定了解集不能包含重复的组合,而上述的算法中并没有去除重复的组合。

因此,需要改进上述算法,在求出组合的过程中就进行去重的操作。考虑将相同的数放在一起进行处理,如果数 x 出现了 y 次,那么在递归时一次性地处理它们,即分别调用选择 0,1,⋯ ,y 次 x 的递归函数。这样就不会得到重复的组合。
    使用一个哈希映射(HashMap)统计数组 arr 中每个数出现的次数。在统计完成之后,将结果放入一个列表 nums 中,方便后续的递归使用。列表 nums 的长度即为数组 arr 中不同数的个数。其中的每一项对应着哈希映射中的一个键值对,即某个数以及它出现的次数。
    在递归时,对于当前的第 pos 个数,它的值为 nums[pos][0],出现的次数为 nums[pos][1],那么可以调用 combinationSumIIDFS(pos+1,rest−i×nums[pos][0])
    即选择了这个数 i 次。这里 i 不能大于这个数出现的次数,并且 i×nums[pos][0] 也不能大于 rest。同时,我们需要将 i 个 nums[pos][0] 放入列表中。
这样一来,就可以不重复地枚举所有的组合了。


package com.loo;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CombinationSumII {

    public static List<int[]> nums = new ArrayList<int[]>();
    public static List<List<Integer>> list = new ArrayList<List<Integer>>();
    public static List<Integer> coms = new ArrayList<Integer>();
    
    public static void main(String[] args) {
        int[] arr1 = {2,3,6,7};
        int target = 7;
        List<List<Integer>> lists = getCombinationSumII(arr1 , target);
        for (List<Integer> l : lists) {
            System.out.println(l);
        }
        int[] arr2 = {2,3,5};
        target = 8;
        nums.clear();
        list.clear();
        coms.clear();
        lists = getCombinationSumII(arr2 , target);
        for (List<Integer> l : lists) {
            System.out.println(l);
        }
        int[] arr3 = {10,1,2,7,6,1,5};
        target = 8;
        nums.clear();
        list.clear();
        coms.clear();
        lists = getCombinationSumII(arr3 , target);
        for (List<Integer> l : lists) {
            System.out.println(l);
        }
        int[] arr4 = {2,5,2,1,2};
        target = 5;
        nums.clear();
        list.clear();
        coms.clear();
        lists = getCombinationSumII(arr4 , target);
        for (List<Integer> l : lists) {
            System.out.println(l);
        }
    }
    
    public static List<List<Integer>> getCombinationSumII(int[] arr , int target) {
        if (arr == null || arr.length == 0) {
            return list;
        }
        Arrays.sort(arr);
        for (int n : arr) {
            int len = nums.size();
            if (nums.isEmpty() || n!=nums.get(len-1)[0]) {
                nums.add(new int[]{n , 1});
            } else {
                ++nums.get(len-1)[1];
            }
        }
        combinationSumIIDFS(0 , target);
        return list;
    }
    
    public static void combinationSumIIDFS(int pos , int target) {
        if (target == 0) {
            list.add(new ArrayList<Integer>(coms));
            return;
        }
        if (pos == nums.size() || nums.get(pos)[0] > target) {
            return;
        }
        combinationSumIIDFS(pos + 1 , target);
        int times = Math.min(target/nums.get(pos)[0], nums.get(pos)[1]);
        for (int i=1;i<=times;i++) {
            coms.add(nums.get(pos)[0]);
            combinationSumIIDFS(pos+1 , target-i*nums.get(pos)[0]);
        }
        for (int i=1;i<=times;i++) {
            coms.remove(coms.size()-1);
        }
    }

}

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值