算法题目 -- 组合总和

202 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

组合总和

组合总和,指给你一个目标数据target,让你从一个整数数组candidates中选取不同的组合进行相加,使得数与目标数据相同,且同一个数据可以无限制重复被选取。我们的目标是,求出有几种组合情况并返回每种情况。

在该题目其实还有一个隐藏条件,就是每个整数数组都是都是按升序排序的。

解题思路

官方解法主要是回溯法,还有一个大佬用了回溯+剪枝法。这两个方法今天我们不细讲,以后找个机会写篇文章。

图片.png

观察类似选取数据的题目时,我们需要找到一种逻辑上是有规律的、有逻辑的组合方式来选取数据。保证这种方式能够避免数据重复、数据冗余。如果想象力丰富的还可以把题目内容类比成生活中的某些事件,以便我们更好的找到他的特性。

我们可以把我们的目标数据total想像成一个桶,total的值就代表了桶的容量,把整数数组candidates里的数据想象成一瓶瓶不同容量的空瓶,他们从小到大依次排列好。我们的目标是先用空瓶装满水,然后再把装满水的空瓶倒入到桶中。

这样我们就把题目变成了一个可想象的、可yy的事件。对于拥有贪心思想的我而言,我肯定先用最大的瓶子来装水,这样装水的次数就少了。注意!瓶子是可重复使用的,因此我会先用最大的瓶子一直装,直到再倒一次桶会溢出时结束。这段可以用一行代码表示:

target / candidates[candidates.length - 1]

那么,桶还需要装入的水量就可以表示为:

target % candidates[candidates.length - 1]

你看,复杂的事物用代码来表达就会变得简单【虽然大多数时候是更加复杂】。在这里我要先让大家了解几个参数:

  • rem[]数组,它代表刚好把桶装满,需要该水瓶的数量。例如rem[1]=2,就是要2个candidates[i]。
  • mod,代表在注入水后,桶中还需注入的水量

回归正题,若桶还没满,我们只需要拿取容量最大的且比上一个瓶子要小的,同时用选中的瓶子装水不会使桶溢出的瓶子即可。

那么有没有一种可能,我是说可能,这样不断迭代下去直到用最小的瓶子也不能刚好装满桶? 那么我们就得回到前一步,用上一次装水的瓶子把桶里的水捞出来,此时桶还需装入的水量为:

//candidates[i]代表上一次的瓶子
mod = mod + candidates[i];

因为我们知道用较大的瓶子行不通了,所以就换到了较小的瓶子继续迭代。 经过反复循环迭代,就能在mod=0的时候知道瓶子的使用情况。将数据记录下来并继续循环迭代。

示例

经过了文字讲解,给大伙几个例子和图示理解一下。

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
23 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

图片.png

图片.png

图片.png

图片.png

图片.png

图片.png

图片.png

图片.png

之后继续按照之前的步骤即可完成循环迭代。下面贴代码出来

代码

class Solution {

    List<List<Integer>> ans = new ArrayList<>();
    
    int[] rem;
    
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        rem = new int[candidates.length];
        calculate(candidates,target,candidates.length);
        return ans;
    }

    public void calculate(int[] candidates, int target, int range) {
        //因为是升序排序,从后往前
        for (int i = range - 1; i >= 0; i--) {
            //代表当前数字最多可以被选入几次
            rem[i] = target / candidates[i];
            //代表被填充完后还剩下哪些部分需要填充
            int mod = target % candidates[i];
            //如果当前字段有多余的
            while (rem[i] > 0) {
                if(mod == 0){
                    //如果刚好取模为0
                    List<Integer> temp = new ArrayList<>();
                    for(int j = 0; j < candidates.length ; j++){
                        int numCount = rem[j];
                        //添加rem里记录的数据
                        while(numCount-- > 0){
                            temp.add(candidates[j]);
                        }
                    }
                    ans.add(temp);
                }else{
                    calculate(candidates, mod, i);
                }
                //回到上一步
                rem[i]--;
                //重新计算需要的水量
                mod += candidates[i];
            }
        }
    }
}