Leetcode2542-最大子序列的分数

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个数进行计算,看到下文便可知

java 复制代码
        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,具体代码如下

java 复制代码
    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数组中

相关推荐
转码的小石1 分钟前
12/21java基础
java
不想当程序猿_5 分钟前
【蓝桥杯每日一题】求和——前缀和
算法·前缀和·蓝桥杯
李小白6610 分钟前
Spring MVC(上)
java·spring·mvc
落魄君子17 分钟前
GA-BP分类-遗传算法(Genetic Algorithm)和反向传播算法(Backpropagation)
算法·分类·数据挖掘
GoodStudyAndDayDayUp23 分钟前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea
菜鸡中的奋斗鸡→挣扎鸡25 分钟前
滑动窗口 + 算法复习
数据结构·算法
Lenyiin34 分钟前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
郭wes代码41 分钟前
Cmd命令大全(万字详细版)
python·算法·小程序
装不满的克莱因瓶1 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
scan7241 小时前
LILAC采样算法
人工智能·算法·机器学习