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數組中