LC每日一题|20240422 - 377. 组合总和 Ⅳ

102 阅读3分钟

LC每日一题|20240422 - 377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

提示:

  • 1 <= nums.length <= 200
  • 1 <= nums[i] <= 1000
  • nums 中的所有元素 互不相同
  • 1 <= target <= 1000

题目级别:Medium

进阶: 如果给定的数组中含有负数会发生什么?问题会产生何种变化?如果允许负数出现,需要向题目中添加哪些限制条件?

解题思路

看到「组合总和」,我的第一反应是...

「回溯就完事儿了!」

一顿输出,点击提交!正在我满怀期待的等待AC的时候,突然屏幕上蹦出一段鲜红的Error...

图片.png

TLE了...而且并不是在一个边缘用例上崩的。这种情况一般代表方案选择的有问题,没有调的必要,直接重开。

我们知道,回溯的本质是暴力,如果有其他的方案,我们尽可能还是不要考虑暴力。话说其实题干中已经给出了提示,「题目数据保证答案符合 32 位整数范围」,明显是对暴力不友好的。

不卖关子了,这题实际上是 70. 爬楼梯 的进阶。

我们就以TLE的这个用例为例。

对于一组特定的 nums , 我们可以设 f(n) 代表target为 n 时组合的个数,此时显见:

f(32) = f(28) + f(30) + f(31)

这个式子是怎么来的呢?

我们以f(28)为例,对于所有满足target == 32的排列来说,其最后一位一定是[4, 2, 1]中的一个。如果它的最后一个数字是4,那么在这个4之前的所有组合方式就是 target28 时对应的组合方式,其数量就是f(28)f(30), f(31)同理,三种情况的加和就是target32的所有情况。

f(28)又可以从f(24)f(26)f(27)加和得出,这里不再赘述。

这和 70. 爬楼梯 如出一辙。

AC代码

递归写法:

class Solution {
    val map = HashMap<Int, Int>()
    fun combinationSum4(nums: IntArray, target: Int): Int {
        if (target < 0) return 0
        if (target == 0) return 1
        if (map[target] != null) return map[target]!!
        var res = 0
        for (i in nums.indices) {
            res += combinationSum4(nums, target - nums[i]).apply {
                map[target - nums[i]] = this
            }
        }
        return res
    }
}

迭代写法:

class Solution {
    val map = HashMap<Int, Int>()
    fun combinationSum4(nums: IntArray, target: Int): Int {
        val dp = IntArray(target + 1)
        dp[0] = 1
        for (i in 1 .. target) {
            nums.forEach {
                if (it <= i) dp[i] += dp[i - it]
            }
        }
        return dp.last()
    }
}

进阶

如果给定的数组中含有负数会发生什么?问题会产生何种变化?如果允许负数出现,需要向题目中添加哪些限制条件?

不难想象有一种极端的情况,对于0 <= i < j < nums.size,如果存在num[i] + nums[j] == 0,那么单一组合的长度就可以是无限长的了,这是非常离谱的。