目录
题目:
示例:
分析:
题目给我们两个长度一样的数组,让我们再num1中找出一个长度为k的子序列,然后把这个子序列累加的和乘上在nums2中对应下标的子序列里的最小值,这边两个子序列的下标在原数组中是一模一样的,要求nums1子序列的和乘上nums2子序列最小值的和要最大。
我们先分析,我们要求的值是受两个因素影响的,第一个就是nums1中子序列的和,第二个就是nums2中子序列的最小值。我们要寻找nums1中和最大的子序列那倒还好找,不不过我们还要兼顾nums2中子序列的最小值,不然nums1的子序列是大了,nums2中最小值给你来个0,那还是白搭。
另外题目还算是降低难度了,有保证数组的值都是非负数,所以我们不必考虑负数的情况。
根据我们上面的推断,nums2中子序列的最小元素的权重比nums1中子序列的元素更高,因此我们选择对nums2进行排序,但是有个问题,不能直接排序,因为排序以后之前的顺序就乱了,而题目是要求两个数组的子序列是要求在原数组中的下标是要一致的。
那我们还能排序吗?
答案是可以的,我们只需要对下标排序就好了。
我们另外拿一个数组来存放下标,然后对这个数组按照nums2的大小进行排序,这么说有点不好理解,大家可以结合着动图看一下。
排完序的存放下标的数组映射到nums2中就是排完序的nums2了,而nums2是没有发生改变的,我们直接遍历存放下标的数组就可以得到从小到大或是从大到小的nums2了。
那么我们就解决了nums2的问题,接下来就剩下了nums1,我们需要让nums1的子序列总和尽可能的大,但是没有要求是要连续的子序列,所以我们直接获取nums1中最大的前k个数就行。不过由于nums2的子序列权重较大,所以我们应该优先考虑nums2的子序列,所以nums1的获取就不能只取nums1中最大的元素,还应该考虑上nums2。
那么说了一大堆,我们到底应该怎么处理呢?
我们可以直接按照nums2递减的下标来获取nums1的元素,而不去管这个nums1的元素是多大多小,我们直接把元素塞进小顶堆里,然后累加到小顶堆内元素总和表示nums1子序列的元素中和,每次小顶堆大小超过K了,我们就吐出一个数,保证nums1的子序列长度为K,并将nums1子序列总和减去这个数,因为是小顶堆,所以每次吐出的数的整个小顶堆里最小的,那么我们留在小顶堆里的元素就是nums1里较大的元素了。
每次我们是从大到小遍历nums2的下标,所以我们每次遍历到的下标在nums2里的元素,就是nums2子序列里的最小元素,所以只要当小顶堆的长度等于K了,我们就把累加的nums1子序列的元素总和乘上当前遍历到的nums2的元素,再去更新最终答案为较大的值即可。
另外可能还会有小伙伴有疑问,为什么按照nums2从大到小的顺序去遍历下标呢?明明我们需要nums2子序列里的元素最小值越大越好,而按照从大到小的顺序,由于刚开始小顶堆的长度小于K,所以我们是没法更新答案的,也就是没法利用到nums2里较大的元素的。
其实我们不可能用到nums2中前K-1大的元素,因为子序列长度是固定死为K的,那么我们就算我们把nums2中最大的元素都塞进去了,那么最多也只能用到第K大的元素,所以我们按照nums2从大到小的顺序遍历下标,如果是反过来的,我们就没法保证我们用的nums2元素是子序列里最小的元素了。
代码:
class Solution {
public:
long long maxScore(vector<int>& nums1, vector<int>& nums2, int k) {
int n = nums1.size();
vector<int>temp(n);
for (int i=0;i<n;i++) temp[i]=i; //初始化一下每个位置的下标
//对下标进行排序,从而不改变原数组(nums2)
sort(temp.begin(),temp.end(),[&](const int &i,const int &j){
return nums2[i]>nums2[j];
});
priority_queue<int,vector<int>,greater<int>> minHeap;//小顶堆
long long sum=0,res=0;
for (int &t:temp){
int x=nums1[t];
int y=nums2[t];
while (minHeap.size()>k-1){ //保持小顶堆(子序列)的长度不大于k
sum-=minHeap.top();
minHeap.pop();
}
minHeap.push(x);
sum+=x;
if (minHeap.size()==k){ //保证小顶堆(子序列)的长度等于k
res=max(res,sum*y);
}
}
return res;
}
};