力扣第三十九题-组合总和

643 阅读3分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

前言

力扣第三十九题 组合总和 如下所示:

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。

candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。 

对于给定的输入,保证和为 target 的唯一组合数少于 150 个。

示例 1:

输入: candidates = [2,3,6,7], target = 7
输出: [[7],[2,2,3]]

示例 2:

输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]

提示:

candidate 中的每个元素都是独一无二的

一、思路

这一题思路很简单,就是在数组中找到所有和为 target 的组合(数值中的数字可重复选取)

题目中有两个比较重要的信息:

  1. 数组中无重复元素
  2. 数组中的值可重复选取

根据上面两个信息所以在递归时需要注意以下两点:

  1. 因为数组中无重复元素,所以在递归时无需考虑结果集中是否有重复的结果
  2. 因为数组中的值可重复选取,所以递归的起点应跟循环中的起点一致。(假设结果中的第一个元素选择以下标 1 作为起点,则第二个元素也需要以下标 1 作为起点)

举个例子

此处以示例1中的 candidates = [2,3,6,7], target = 7 作为例子

tips:遍历按照从左至右的顺序,使用 来存储遍历的路径

  1. 选取第一个元素 2,此时 2 < 7,元素 2 入栈
  2. 递归1:继续选取第一个元素 2,此时 (2 + 2) < 7,元素 2 入栈
  3. 递归2:继续选取第一个元素 2,此时 (2 + 2 + 2) < 7,元素 2 入栈。此时栈为 2 -> 2 -> 2
  4. 递归3:继续选取第一个元素 2,此时 (2 + 2 + 2 + 2) > 7,无需入栈,且无需向下递归
  5. 递归3:继续选取第二个及之后的元素。很显然没有满足的结果,故栈顶出栈。此时栈为 2 -> 2,此层递归结束
  6. 递归2:继续选择第二个元素 3,此时 (2 + 2 + 3) == 7,此时找到了第一个正确的结果 [2, 2, 3]
  7. ......,其他的结果递归的过程与第一个结果类似
  8. 最终可以得到正确的结果: [2, 2, 3], [7]

二、实现

实现代码

实现代码与思路中有一点不一样:

为了避免遍历栈来获取总和,所以直接将下一次递归的 target 置为 target - candidates[i],如果 target == 0 则代表找到了一个正确的结果

/**
 * 回溯算法
 */
public List<List<Integer>> combinationSum(int[] candidates, int target) {
    List<List<Integer>> ret = new ArrayList<>();
    int len = candidates.length;
    if (len == 0)
        return ret;
    Stack<Integer> path = new Stack<>();
    dfs(candidates, 0, target, path, ret);
    return ret;
}

/**
 * 回溯的实现
 * @param candidates    候选数组
 * @param begin 开始搜索位置
 * @param target    目标值
 * @param path  路径
 * @param ret   结果集
 */
private void dfs(int[] candidates, int begin, int target, Stack<Integer> path, List<List<Integer>> ret) {
    // 剪枝:target为负值和0时不分支
    if (target < 0)
        return;
    if (target == 0) {
        ret.add(new ArrayList<>(path));
    }

    // 从begin开始搜索
    for (int i=begin; i<candidates.length; i++) {
        // 路径进栈
        path.push(candidates[i]);
        // 因为会重复选取,所以下次搜索起点为i
        dfs(candidates, i, target - candidates[i], path, ret);
        // 路径出栈
        path.pop();
    }
}

测试代码

public static void main(String[] args) {
    int[] nums = {2,3,6, 7};
    new test().combinationSum(nums, 7);
}

结果

1628218338(1).png

三、总结

虽然这一题是写出来了,但是击败率不是很理想。

看了一下别人写的算法,采用的是 先排序 + 再剪枝,可以有效的减少递归的次数。(非常不错的思路,点赞~)

感谢看到最后,非常荣幸能够帮助到你~♥