给你一个下标从 0 开始的整数数组 nums ,它表示英雄的能力值。如果我们选出一部分英雄,这组英雄的 力量 定义为:
i0 ,i1 ,… ik 表示这组英雄在数组中的下标。那么这组英雄的力量为 max(nums[i0],nums[i1] … nums[ik])2 * min(nums[i0],nums[i1] … nums[ik]) 。
请你返回所有可能的 非空 英雄组的 力量 之和。由于答案可能非常大,请你将结果对 109 + 7 取余。
示例 1:
输入:nums = [2,1,4]
输出:141
解释:
第 1 组:[2] 的力量为 22 * 2 = 8 。
第 2 组:[1] 的力量为 12 * 1 = 1 。
第 3 组:[4] 的力量为 42 * 4 = 64 。
第 4 组:[2,1] 的力量为 22 * 1 = 4 。
第 5 组:[2,4] 的力量为 42 * 2 = 32 。
第 6 组:[1,4] 的力量为 42 * 1 = 16 。
第 7 组:[2,1,4] 的力量为 42 * 1 = 16 。
所有英雄组的力量之和为 8 + 1 + 64 + 4 + 32 + 16 + 16 = 141 。
示例 2:
输入:nums = [1,1,1]
输出:7
解释:总共有 7 个英雄组,每一组的力量都是 1 。所以所有英雄组的力量之和为 7 。
提示:
1 <= nums.length <= 105
1 <= nums[i] <= 109
题意:
就是要找出所有的子序列,每个子序列都要算出一个结果值,这个结果值是这个子序列的最大值的平方乘以最小值。
思路:
因为只要返回所有的结果就行,所以可以先将原数组从小到大排序,这样的话每一个数字都是当前序列的最大值。
解决了最大值的问题,下面是最小值。从前向后遍历,当走到位置 i 的时候,以 i 结尾的序列的最大值就是nums[i] , 那最小值是什么呢?
最笨的方法就是从头到 i 在遍历一遍,找到最小值,计算结果。那这样其实是不对的哦,因为他要求的是所有子序列,而你这样做的话序列都是从头开始的,并不是所有的情况。
这里就要用到dp了。为什么会想到dp呢?因为沿着刚才的思路,我们是想每次走到一个位置的时候,只需要从头遍历一遍,就可以知道所有以当前位置结尾的序列的最小值的和。
那要实现这样的想法,每个位置都是由前一个位置的结果变化而来,就是dp的状态转移方程来实现的。
那如何dp呢?假设以位置 i-1 结尾的最小值的和已经计算完,记为dp[i-1],那
dp[i] = dp[j] + nums[i] , 其中 j 是从0到 i-1 。
为什么是这样写的呢?因为以 i 为结尾的序列,有多少种可能呢?那就是它前面的序列有多少种排列,那加上他本身也有多少种,所以就是 0 到 i-1 的所有dp值,此外还有 i 位置自己一个数组成一个序列,所以还要加上 nums[i]。
到此,状态转移方程已经清晰了。那每个位置的结果就是
nums[i]×nums[i]×dp[i]
ok到这里这道题已经可以做出来的,但是还是过不了,因为这样做的话,每个位置还是要从头加到尾,时间复杂度是n方。
但是很好优化,因为超时的原因是每次都要计算该位置到起点的元素和,那只需要用前缀和线性的将和都存下来就好了喔。
其实这样就已经可以AC了,但是当你写完就会发现,每一个dp值只与前一个位置的dp值有关,前缀和也是一样的,只与前一个位置的前缀和有关,所以dp和前缀和只需要用单一变量表示即可,无需开辟数组。
上面分别从时间优化和空间优化进行了阐述,下面奉上两者优化完的最终代码。
class Solution {
public:
int sumOfPower(vector<int>& nums) {
int n = nums.size();
sort(nums.begin() , nums.end());
int dp = 0;
int presum = 0;
int res = 0;
int mod = 1e9+7;
for(int i = 0 ; i < n ; i++){
dp = (nums[i] + presum) % mod;
presum = (presum + dp) % mod;
res = (int)((res + (long long)nums[i] * nums[i] %mod *dp) % mod);
if(res < 0)
res += mod;
}
return res;
}
};