Leetcode 891. 子序列宽度之和
题目描述
一个序列的 宽度 定义为该序列中最大元素和最小元素的差值。
给你一个整数数组 nums
,返回 nums
的所有非空
子序列 的 宽度之和
。由于答案可能非常大,请返回对 109 + 7
取余 后的结果。
子序列
定义为从一个数组里删除一些(或者不删除)元素,但不改变剩下元素的顺序得到的数组。例如,[3,6,2,7]
就是数组 [0,3,1,6,2,2,7]
的一个子序列。
示例 1:
输入:nums = [2,1,3] 输出:6 解释:子序列为 [1], [2], [3], [2,1], [2,3], [1,3], [2,1,3] 。 相应的宽度是 0, 0, 0, 1, 1, 2, 2 。 宽度之和是 6 。
示例 2:
输入:nums = [2] 输出:0
提示:
-
1 <= nums.length <= 105
-
1 <= nums[i] <= 105
解答
注意阅读题目,题目中说的序列/子序列,实际上是集合和子集合。 将集合排序为序列。 对于每一个数字,贡献最大值的情况为加,贡献最小值的情况为减。 每个数字只对其左边的子集合作最大值,对右边的子集合作最小值。
详情见onenote。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Solution {
public:
const int mod=1000000007;
int sumSubseqWidths(vector<int>& nums) {
long long ans=0;
vector<long long> vpow(nums.size(),1);
for (int i=1;i<nums.size();i++){
vpow[i]=(vpow[i-1]<<1)%mod;
}
sort(nums.begin(),nums.end());
for (int i=0;i<nums.size();i++){
ans+=nums[i]*(vpow[i]-vpow[nums.size()-i-1])%mod;
ans%=mod;
}
return ans;
}
};
【C++】数学(乘法)
假如数组:[2,1,3],因为子序列顺序不会对结果产生影响
比如它的子序列[1], [2], [3], [2,1], [2,3], [1,3], [2,1,3] 跟[1,2,3]的子序列[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]最终结果是一样的
所以我们按照递增排序后讨论,这样每个元素都比它左边大或相等(作为最大值),比它右边小(作为最小值)
那么我们只需要讨论每个元素左右的贡献值,我们不妨拿数字2作为例子,那么它作为最大值贡献了2i次(i=1,排序后),作为最小值贡献了2(n-i-1)次,可以用数学归纳证明
(这里略),注:贡献都包含本身统计,结果相减没影响
结果就是每个数贡献值相加sum(2i-2(n-i-1))*nums[i])
Complexity
- Time: \(O(nlogn)\)
- Space: \(O(n)\)
题外话
- 注意取模分配律