LC每日一题|20240422 - 377. 组合总和 Ⅳ
给你一个由 不同 整数组成的数组
nums,和一个目标整数target。请你从nums中找出并返回总和为target的元素组合的个数。题目数据保证答案符合 32 位整数范围。
提示:
1 <= nums.length <= 2001 <= nums[i] <= 1000nums中的所有元素 互不相同1 <= target <= 1000
题目级别:Medium
进阶: 如果给定的数组中含有负数会发生什么?问题会产生何种变化?如果允许负数出现,需要向题目中添加哪些限制条件?
解题思路
看到「组合总和」,我的第一反应是...
「回溯就完事儿了!」
一顿输出,点击提交!正在我满怀期待的等待AC的时候,突然屏幕上蹦出一段鲜红的Error...
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之前的所有组合方式就是 target 为 28 时对应的组合方式,其数量就是f(28)。f(30), f(31)同理,三种情况的加和就是target为32的所有情况。
而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,那么单一组合的长度就可以是无限长的了,这是非常离谱的。