1.问题转换
首先明确题意,要选取的值和num1,num2两个数组都有关,但是num1中选取的是k个数,num2中选取的是1个数,显然num2中的数所占的权重较大(对结果影响较大),所以我们就可以对num2进行排序(也可以对nums1进行排序,就是对nums1排列以后枚举时获取nums2最小值特麻烦,就不再赘述了,有兴趣的读者可以思考一下),枚举num2中的每个数,然后确定num1中对应的k个数,但是选取元素时 num1 和 num2 对应的索引要一样,所以不能对num2直接排序,那么就对num2所对应的索引进行排序即可,对num2的索引,按照num2的值从大到小进行排序,为什么从大到小,因为要过滤在num2中前k-1个数,在第k个数进行计算,看到下文便可知
int len = nums1.length;Integer[] ids = new Integer[len];for (int i = 0; i < len; i++) {ids[i] = i;}//按照nums2[] 数组元素降序后排列的下标Arrays.sort(ids, (i, j) -> nums2[j] - nums2[i]);
进行这样的排序之后,所得到的效果就是 nums2[ids[0]] 就是nums2中最大的元素,nums2[ids[1]]就是num2中第二大的元素...
要设计一个小顶堆,确保这k个数在遍历时,是遍历到的最大值,如果每次遇到一个值比堆顶元素大,那么就替换堆顶元素,并且定义一个变量 sum 记录堆中元素的总和,便于计算
2. 要理解的三个点
A. nums2[ids[i]] i 从 0 -> len - 1 遍历 nums2[ids[i]] 就是降序的
B. 要从nums2[] 中第k大的元素 x 开始遍历,如果选了前面的数(比x大的数),那么nums1[] 就凑不出k个数满足配件,例如图片中的例子,如果选了nums2中最大的数4,对应的下标只有一个3,就凑不出3个下标,因为4在nums2中就是最大的,不存在两个比4小的数
C. nums1[] nums2[] 中选取的下标都是一样的,nums2[ids[i]] 选取的下标是 ids[i] 那么nums1[] 选取的下标也得是ids[i], 所以先把 前k个 ids[i] 所以对应的nums1[] 的元素入小顶堆
3. 代码编写
首先就是将num2的最大值索引映射到ids上,这样 i 从 0 -> len - 1 遍历 nums2[ids[i]] 就是降序的,因为必须从num2中的第k个元素开始计算(至于为什么,看第二点),所以就跳过前k个num2中最大的数(跳过的索引为ids[0....k-1]),对应的就把num1[ids[0.....k-1]] 这些元素入堆,并且计算和,此时已经有第一个结果,就定义res存储这个结果。
因为通过k你已经确定了nums2的最大值了,因为位置是共同变换的,所以相应的nums1的和就是初始值,但是这个答案不一定是最大的,那么我们就需要往后选,num2往后选必然会越来越小,所以影响答案的是num1新加的数,不光要维护nums2最小值,还要维护nums1的和,每次都会新加一个数,小根堆维护的最小的k个元素,当加入的元素要比最小的小的话就更新
然后就是遍历剩下的nums2中的len-k个元素,也就是比nums2[ids[k-1]] 小的元素,此时对应的小顶堆中维护的num1[] 中的值也应该发生变化,因为nums2[] 的索引发生了变化,如果nums1[ids[i]] > minHeap.peek() 那么就弹出堆顶元素,将nums1[ids[i]]入堆,确保堆中元素是遍历过的元素里面最大的k个元素,同时更新res和sum,具体代码如下
public static long maxScore(int[] nums1, int[] nums2, int k) {//需要以及难理解的3点://1. nums2[ids[i]] i 从 0 -> len - 1 遍历 nums2[ids[i]] 就是降序的//2. 要从nums2[] 中第k大的元素 x 开始遍历,如果选了前面的数(比x大的数),那么nums1[] 就凑不出k个数满足配件//3. nums1[] nums2[] 中选取的下标都是一样的,nums2[ids[i]] 选取的下标是 ids[i] 那么nums1[] 选取的下标也得是ids[i]// 所以先把 前k个 ids[i] 所以对应的nums1[] 的元素入小顶堆int len = nums1.length;Integer[] ids = new Integer[len];for (int i = 0; i < len; i++) {ids[i] = i;}//想要对nums2[] 进行排序,但是对应的索引不能边,就对索引按照nums2的元素从大到小进行排序Arrays.sort(ids, (i, j) -> nums2[j] - nums2[i]);//从 0 -> len 遍历nums2[ids[i]] 就得到的是nums2从大到小遍历的结果//直接获取nums1中最大的前k个数即可PriorityQueue<Integer> minHeap = new PriorityQueue<>();long sum = 0;for (int i = 0; i < k; i++) {sum += nums1[ids[i]];minHeap.add(nums1[ids[i]]);}// 枚举的nums2[] 中的最大值,一定不是整个数组的最大值,而是nums2中的第k大的值,// 这样的话,nums1中才能找到k个与之对应的元素,如果找nums2中最大值,那么对应的nums1中的值只有一个// 所以必须得从nums2的第k大个元素开始,枚举的num2一直变小,然后对应的minHeap中的值变大long res = sum * nums2[ids[k - 1]];for (int i = k; i < len; i++) {int x = nums1[ids[i]];if (x > minHeap.peek()) {sum += x - minHeap.poll();minHeap.add(x);res = Math.max(res, nums2[ids[i]] * sum);}}return res;}
4.总结
说实话,这道题我认为还是挺不好理解的,我自己刷的时候也思考了很久,这个问题转换是这道题的核心,需要注意的三个点必须理清楚(尤其是必须从第k大的元素开始计算,还有两个数组所选取元素的索引是一样的),建议读者反复观看
这道题我没见过的点是:想要对一个数组进行排序,但是又想让其对应的索引不变,就创建一个索引数组,让这个索引数组按照待排序数组的元素大小,升序或者降序排列,这样就把num2数组排序后的结果,映射到了ids数组中