LeetCode 39 组合总和

255 阅读3分钟

「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」。

题目:给定一个无重复序列数组nums和一个目标值target,要求 找出nums中所有可能存在的组合,使得组合的和为target,注意:数组中元素可以重复使用且要求得到的结果不能重复。

解题思路

在做这题之前,我首先去做了一下第46题,全排列,如果这题没思路建议也先做全排列那题。

本题是找出所有满足列表之和等于target的排列,并且数组中元素可以重复使用,而且要求得到的列表没有重复。例如数组[2, 3, 6, 7],要求找到所有和为7的列表,则如果2在候选列表中,我们的目标就是在数组中找到和为7-2的列表。如果3在候选列表中,我们就要找到和为7-3的列表。以此类推。

上述思路可以使用递归实现,和全排列一样,设置全局list和局部list,当递归满足一定条件时,全局list保存局部list结果。此处的条件为target==0,之后遍历数组,调用回溯过程,在调用的过程中必须保证target始终大于等于0即可,代码如下:

private ArrayList<List<Integer>> combinResult = new ArrayList<>();

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        ArrayList<Integer> arr = new ArrayList<>();
        backtrace(arr, candidates, target);
        return combinResult;
    }

    public void backtrace(ArrayList<Integer> arr, int[] candidates, int target){
        if(target==0){
            combinResult.add(new ArrayList<>(arr));
            return;
        }

        for(int i=0;i<candidates.length;i++){
            if(target>=0){
                arr.add(candidates[i]);
                backtrace(arr, candidates, target-candidates[i]);
                arr.remove(arr.size()-1);
            }
        }
    }

由于回溯每次都是从0开始调用,该方法会得到很多重复解,例如对于数组[2, 3, 6, 7],其最终结果为:[[2,2,3],[2,3,2],[3,2,2],[7]],但[[2,2,3],[2,3,2],[3,2,2]]实际上重复了,重复的结果可以通过对列表中每个列表排序,加入map,之后map的最终结果即为本题结果。但此方法会增加额外空间和时间,较为繁琐。

我们分析重复结果出现的原因,在以每个元素作为初始候选点后,我们对下一个候选点的选择是数组全集,这样当前面的数字有了合适的结果后(这个结果包含数组后面元素),后面的元素如果候选集合仍然是整个数组,那么必然会和前面的元素进行组合,此时就得到了两个重复解。 实际上,我们可以在遍历的时候就避免这种情况,当以第i个元素为首位候选集时,下一个候选集也是从第i个元素开始的,这样就能满足元素可以重复使用的条件,同时保证不会重复,代码如下:

private  ArrayList<List<Integer>> combinResult = new ArrayList<>();
    public  List<List<Integer>> combinationSum(int[] candidates, int target) {
        ArrayList<Integer> arr = new ArrayList<>();
        backtrace(0, arr, candidates, target);
        return combinResult;
    }

    public void backtrace(int begin, ArrayList<Integer> arr, int[] candidates, int target){
        if(target==0){
            combinResult.add(new ArrayList<>(arr));
            return;
        }

        for(int i=begin;i<candidates.length;i++){
            if(target>=0){
                arr.add(candidates[i]);
                backtrace(i, arr, candidates, target-candidates[i]);
                arr.remove(arr.size()-1);
            }
        }
    }

对于本题的思考,题解中有位大佬解释的很好:回溯算法 + 剪枝(回溯经典例题详解)