3428. 至多 K 个子序列的最大和最小和
题目地址(3428. 至多 K 个子序列的最大和最小和 - 力扣(LeetCode))
题目描述
给你一个整数数组 nums
和一个整数 k
,请你返回一个整数,表示从数组中选取 至多 k 个子序列,所有可能方案中,子序列的 最大值之和 加上 最小值之和 的结果。由于结果可能很大,请返回对 (10^9 + 7) 取模后的值。
一个数组的 子序列 是指通过删除一些(可以是 0 个)元素后剩下的序列,且不改变其余元素的相对顺序。例如,[1, 3]
是 [1, 2, 3]
的子序列,而 [2, 1]
不是。
示例 1:
示例 2:
提示:
(1 \leq nums.length \leq 10^5)
(1 \leq nums[i] \leq 10^9)
(1 \leq k \leq 10^5)
前置知识
组合数学:组合数 (C(n, m)) 表示从 (n) 个元素中选 (m) 个的方案数。
贡献法
思路
这道题要求计算所有至多 (k) 个子序列的最大值之和与最小值之和。数组的顺序对每个元素的贡献没有任何影响,因此我们可以先对数组进行排序,然后计算每个元素作为最大值或最小值的贡献。
我们可以从贡献的角度来思考:对于数组中的每个元素,它在所有可能的子序列中作为最大值或最小值的次数是多少?然后将这些次数乘以元素值,累加起来即可。
分析
子序列的性质:
一个子序列的最大值是其中最大的元素,最小值是最小的元素。
对于一个有序数组 (nums),若元素 (nums[i]) 是子序列的最大值,则子序列只能从 (nums[0]) 到 (nums[i]) 中选取,且必须包含 (nums[i])。
若 (nums[i]) 是子序列的最小值,则子序列只能从 (nums[i]) 到 (nums[n-1]) 中选取,且必须包含 (nums[i])。
组合计数:
假设数组已排序(从小到大),对于 (nums[i]):
作为最大值的子序列:从前 (i) 个元素中选 (j) 个((0 \leq j < \min(k, i+1))),再加上 (nums[i]),总方案数为 (\sum_{j=0}^{\min(k, i)} C(i, j))。
作为最小值的子序列:从后 (n-i-1) 个元素中选 (j) 个((0 \leq j < \min(k, n-i))),再加上 (nums[i]),总方案数为 (\sum_{j=0}^{\min(k, n-i-1)} C(n-i-1, j))。
这里 (C(n, m)) 表示组合数,即从 (n) 个元素中选 (m) 个的方案数。
优化组合计算:
由于 (n) 和 (k) 可达 (10^5),直接用 (math.comb) 会超时,且需要取模。
使用预计算阶乘和逆元的方法,快速计算 (C(n, m) = n! / (m! \cdot (n-m)!) \mod (10^9 + 7))。
最终公式:
对每个 (nums[i]),计算其作为最大值的贡献和最小值的贡献,累加后取模。
步骤
对数组 (nums) 排序。
预计算阶乘 (fac[i]) 和逆元 (inv_f[i])。
遍历 (nums):
计算 (nums[i]) 作为最大值的次数,乘以 (nums[i]),加到答案中。
计算 (nums[i]) 作为最小值的次数,乘以 (nums[i]),加到答案中。
返回结果对 (10^9 + 7) 取模。
代码
代码支持 Python3:
Python3 Code:
复杂度分析
时间复杂度:(O(n \log n + n \cdot k))
排序:(O(n \log n))。
预计算阶乘和逆元:(O(MX)),(MX = 10^5) 是常数。
遍历 (nums) 并计算组合和:(O(n \cdot k)),因为对于每个 (i),需要计算最多 (k) 个组合数。
空间复杂度:(O(MX)),用于存储阶乘和逆元数组。
总结
这道题的关键在于理解子序列的最大值和最小值的贡献,并利用组合数学计算每个元素出现的次数。预计算阶乘和逆元避免了重复计算组合数的开销,使得代码能在时间限制内运行。排序后分别处理最大值和最小值贡献,是一个清晰且高效的思路。
如果你有其他解法或疑问,欢迎讨论!
最后更新于
这有帮助吗?