【Leetcode】216. 组合总和 III——递归 + 回溯(优于标答,击败100%时间 + 97.83%内存)

题目

  • 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
  • 说明:
    • 所有数字都是正整数。
    • 解集不能包含重复的组合。
  • 示例1:

输入: k = 3, n = 7
输出: [[1,2,4]]

  • 示例2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

解题思路

  1. 在方法的开始,首先先确定所给定的k和n是否能够有解

    • 给定k个数,那么这k个数的最小值为1 + 2 + … + k = (1 + k) * k / 2 //首项为1,公差为1的等差数列;
    • k个数的最大值为(n - k + 1) + (n - k + 2) + … + n = (19 - k) * k / 2 //末项为9,公差为1的等差数列;
    • 由此可得,只有当n >= (1 + k) * k / 2 并且 n <= (19 - k) * k / 2时才会有解,如果k和n不满足条件,则直接返回空列表。
  2. 核心思想

    • 现在需要用k个数之和表示n:
      • 当k = 1时,target = n,list中直接放入n;
      • 当k = 2时,假定先在list中放入一个数i(1<=i<=9),那么还需要放入n - i,相当于调用k = 1情况,此时target = n - i;
      • 当k = 3时,假定先在list中放入一个数i(1<=i<=9),那么还需要放入n - i,相当于调用k = 2情况,此时target = n - i;
      • ·······
      • 由此,我们可以发现一个规律:当我们在计算某一个k值的情况时,可以先放入一个元素i,然后递归调用target = n = i的k - 1情况,以此类推,递归终止条件即为k = 1的情况,此时如果target <= 9,那么放入target后即为我们想要的解。
      • 总公式:第k个list[target = n] = [i].append(第k - 1个list[target = n - i])
  3. 开始计算

    • 方法中维护了四个变量:rest(剩余数字个数),target(当前和还缺多少),sum(题目需要求的总和,也可使用全局变量记录,currentSum(当前列表中已有元素的和,也可使用sum - target计算)
    • 首先,当剩余个数为1,但target仍大于9(rest == 1 && target > 9),即还剩一个元素的位置,但却需要一个大于9的数来满足题意,无法达到目标,因此直接return,放弃此情况。
    • 其次,如果剩余个数为1,并且加上当前target后能够达到sum值,那么此时的target就是我们需要的最后一个元素,将其加入答案res,加入之后移除末尾元素,以便进行后续运算。
    • 剩余部分请看代码中的注释。

代码

class Solution {

    // res存放最终答案
    List<List<Integer>> res = new ArrayList<>();

    // temp存放每一个解情况
    List<Integer> temp = new ArrayList<>();

    public List<List<Integer>> combinationSum3(int k, int n) {
        // 判断此时是否有解
        if (n >= (1 + k) * k / 2 && n <= (19 - k) * k / 2) {
            calculate(k, n, n, 0);
        }
        return res;
    }

    public void calculate(int rest, int target, int sum, int currentSum) {

        // 如果此时只剩一个数字,而缺少的和大于9,已无法实现,直接return舍弃
        if (rest == 1 && target > 9) {
            return;
        }

        // 如果剩余一个元素,且加上target后当前和currentSum等于要求的sum,则输出
        // 第二个条件是为了保证最后一个元素大于倒数第二元素
        // (排除诸如[1, 4, 9, 9],[1, 3, 7, 7]的有重复数字的情况)
        else if (rest == 1 && temp.get(temp.size() - 1) < target && currentSum + target == sum) {
            temp.add(target);
            res.add(List.copyOf(temp));
            // 移除情况,为后续递归做准备
            temp.remove(temp.size() - 1);
        }

        // 此时的情况为:剩余元素大于1,或者currentSum + target != sum
        else {
            // 如果是由于后者情况,那么此时为一个错误解,return舍弃
            if (rest == 1) {
                return;
            } else {
                // 如果是前者情况,那么此时仍可放入至少两个元素,从当前temp的末尾元素加1开始遍历
                // 如果当前temp为空,则从0开始遍历,依次尝试放入
                for (int i = (temp.size() == 0 ? 1 : temp.get(temp.size() - 1) + 1); i <= target && i <= 9; i++) {
                    temp.add(i);
                    // 放入i后,剩余可放入元素rest减1,新的target变成target - i,当前和currentSum + i
                    calculate(rest - 1, target - i, sum, currentSum + i);
                    // 回到此处继续执行,说明最后一个元素的加入后的情况已全部遍历,将此元素移除
                    temp.remove(temp.size() - 1);
                }
            }
        }
    }
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
题目描述: 给定一个只包含正整数的非空数组,是否可以将这个数组分成两个子集,使得两个子集的元素和相等。 示例: 输入:[1, 5, 11, 5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11]。 解题思路: 这是一道经典的 0-1 背包问题,可以使用动态规划或者回溯算法解决。 使用回溯算法,需要定义一个 backtrack 函数,该函数有三个参数: - 数组 nums; - 当前处理到的数组下标 index; - 当前已经选择的元素和 leftSum。 回溯过程中,如果 leftSum 等于数组元素和的一半,那么就可以直接返回 true。如果 leftSum 大于数组元素和的一半,那么就可以直接返回 false。如果 index 到达数组末尾,那么就可以直接返回 false。 否则,就对于当前元素,有选择和不选择两种情况。如果选择当前元素,那么 leftSum 就加上当前元素的值,index 就加 1。如果不选择当前元素,那么 leftSum 不变,index 也加 1。最终返回所有可能性的结果中是否有 true。 Java 代码实现: class Solution { public boolean canPartition(int[] nums) { int sum = 0; for (int num : nums) { sum += num; } if (sum % 2 != 0) { return false; } Arrays.sort(nums); return backtrack(nums, nums.length - 1, sum / 2); } private boolean backtrack(int[] nums, int index, int leftSum) { if (leftSum == 0) { return true; } if (leftSum < 0 || index < 0 || leftSum < nums[index]) { return false; } return backtrack(nums, index - 1, leftSum - nums[index]) || backtrack(nums, index - 1, leftSum); } }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

MomentNi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值