🌈【LeetCode.最小的K对数字】- JavaScript =>优先队列+最小堆

264 阅读2分钟

「这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战


说明:文章部分内容及图片出自网络,如有侵权请与我本人联系(主页有公众号:小攻城狮学前端)

作者:小只前端攻城狮、 主页:小只前端攻城狮的主页、 来源:掘金

GitHub:P-J27、 CSDN:PJ想做前端攻城狮

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


题目描述

给定两个以升序排列的整数数组 nums1 和 nums2 , 以及一个整数 k 。

定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。

请找到和最小的 k 个数对 (u1,v1),  (u2,v2)  ...  (uk,vk) 。

示例 1:

输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3 输出: [1,2],[1,4],[1,6] 解释: 返回序列中的前 3 对数: [1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]

示例 2:

输入: nums1 = [1,1,2], nums2 = [1,2,3], k = 2 输出: [1,1],[1,1] 解释: 返回序列中的前 2 对数:   [1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]

优先队列

其实这类的什么第K,前K之类的题目有些类似 Dijkstra 最短路径,我们通过优先队列求解。

假设当前优先队列中最小的是 (u[i],v[j]) => 将 (u[i+1],v[j]) 和 (u[i],v[j+1])入队,因为剩下的组合中这两个一定比其他的组合小。

var kSmallestPairs = function(nums1, nums2, k) {
    const len1 = nums1.length, len2 = nums2.length
    if (len1 === 0 || len2 === 0) return []
    k = Math.min(len1 * len2, k)
    const search = (val) => {
        let left = 0, right = queue.length - 1
        while(left <= right) {
            let mid = Math.floor((right - left)/2) + left
            const [idx1, idx2] = queue[mid]
            if (nums1[idx1] + nums2[idx2] > val) {
                right = mid - 1
            } else if (nums1[idx1] + nums2[idx2] < val) {
                left = mid + 1
            } else {
                return mid
            }
        }
        return left
    }
    const res = []
    // 有序队列
    const queue = nums2.map((_, i) => [0, i])

    while(k > 0) {
        const item = queue.shift()
        res.push([nums1[item[0]], nums2[item[1]]])
        if (item[0] + 1 < len1) {
            item[0] += 1
            // 二分法查找插入位置
            const idx = search(nums1[item[0]] + nums2[item[1]])
            queue.splice(idx, 0, item)
        }
        k--
    }

    return res
};
最小堆

也还可以通过堆来实现,不得不说,堆是真的好用,这里说一下小顶堆的写法。

思路:我们首先构建nums1.size()也就是3个队首元素,在3个队首元素中找到最小的那个元素来维护结果变量res,同时该队指针后移一位,继续比较找出最小的k个元素。

var kSmallestPairs = function(nums1, nums2, k) {
const minHeap =new MinHeap([], (pair1, pair2)=> nums1[pair1[0]] + nums2[pair1[1]] - nums1[pair2[0]] - nums2[pair2[1]] ); 
for(let i = 0; i < Math.min(nums1.length, k); i++){
    minHeap.offer([i, 0]);
    }
    const res = [];
    while(k-- && minHeap.size != 0){
        const pair = minHeap.poll();
        res.push([nums1[pair[0]],nums2[pair[1]]]);
        if(pair[1] < nums2.length - 1){
            minHeap.offer([pair[0], pair[1] + 1]);
        }
    }
    return res;
};
class MinHeap {
  fyUp(index){
        while(index > 0){
            let parentIndex = (index - 1) >> 1;
            if(this.comparator(this.data[index], this.data[parentIndex]) < 0){
                this.swap(index, parentIndex);
                index = parentIndex;
            }else {
                break;
            }
        }
    }
    offer(val){
        this.data.push(val);
        this.heapifyUp(this.size - 1);
    }
    poll(){
        const ret = this.data[0];
        const last = this.data.pop();
        if(this.size != 0){
            this.data[0] = last;
            this.heapifyDown(0);
        }

        return ret;
    }
    peek(){
        if(this.size == 0){
            return 0;
        }
        return this.data[0];
    }
    getData(){
        return this.data;
    }
    getDataByIndex(index){
        return this.data[index];
    }
    get size(){
        return this.data.length;
    }
    swap(index1, index2){
        [this.data[index1], this.data[index2]] = [this.data[index2], this.data[index1]];
    }

}

分析:其实利用堆,都有点像暴力解法,如果为了更好的时间性能和空间性能可以再考虑考虑利用多路归并思想。每一对可表示为 (nums1[i], nums2[j]),将同 i 的视为在同一等待队列中。堆的大小可以优化为 n=min(n1,n2,k)。复杂度:n + k * log(n),第一项为初始化堆的开销。


感谢阅读,希望能对你有所帮助,文章若有错误或者侵权,可以在评论区留言或在我的主页添加公众号联系我。

写作不易,如果觉得不错,可以「点赞」+「评论」 谢谢支持❤