【力扣时间】【373】【中等】查找和最小的K对数字

又到周五了,不过今天的中等题稍微有些动脑哦。

1、读题

我是题

言简意赅。

2、审题

题目给的信息很少,但足够表述清楚题意。
而你也能很轻松地发现这题的考点和难点。

但我们先说重点:

  1. 提供的两个数组是保证升序
  2. 数组内可能存在相同的数字
  3. 两个数组组成的所有数对可能都不够K个(见示例3)

3、思路

最简单无脑暴力的思路,当然是嵌套循环,将两个数组的每个元素组成笛卡尔积,塞到一个优先级队列里,然后依次取出前K个。

很明显,在1 <= nums1.length, nums2.length <= 105 的条件下,这种思路多半是超时的。
即使不超时,耗时排名也会很难堪。

有这种想法绝不是坏事,它虽然朴素,但却能成为好的思路的跳板。
由于题目给出的K的范围最大是1000,所以一股脑把所有元素的笛卡尔积全部塞进去是很浪费的,于是我们可以一个个塞进去。

由于题目保证了nums1nums2是严格升序的,所以很明显,(nums1[0],nums2[0])是最小的数对。
而在此基础上,可能第二小的数对将存在于(nums1[1],nums2[0])(nums1[0],nums2[1])之中。

于是,我们可以实现一个优先级队列,用于存放最小数对的下标,每次取出当前最小的数对,假设为(i,j),则在(i,j)之后,比他稍大一点的数对可能为(i,j+1)(i+1,j),将它们全部添加到优先级队列中,并重复此过程,直到出队的的数量达到K,或队列已空。

4、动手吧!

class Solution {
    public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        List<List<Integer>> result = new ArrayList<>();
        Set<Integer> vis = new HashSet<>();
        Queue<int[]> queue = new PriorityQueue<>((o1, o2) -> nums1[o1[0]] + nums2[o1[1]] - nums1[o2[0]] - nums2[o2[1]]);
        queue.add(new int[]{0, 0});

        while (result.size() < k && !queue.isEmpty()) {
            int[] pair = queue.poll();
            int i = pair[0], j = pair[1];

            int p = i * 10000 + j;
            //已访问则跳过
            if (vis.contains(p)) {
                continue;
            }

            //添加当前对到结果中
            result.add(Arrays.asList(nums1[i], nums2[j]));
            //也标记为已访问
            vis.add(p);

            //当nums1未抵达边界时,提交nums2的当前数和nums1的下一个数
            if (i + 1 < nums1.length) {
                queue.add(new int[]{i + 1, j});
            }
            //当nums2未抵达边界时,提交nums1的当前数和nums2的下一个数
            if (j + 1 < nums2.length) {
                queue.add(new int[]{i, j + 1});
            }
        }

        return result;
    }
}

5、解读

正如我之前所说,我们可以通过优先级队列来实时获取当前最小的数对。
于是,我实现了一个元素为int[]的优先级队列,每一个int[]包含了两个数,即为当前nums1[]nums2[]下标
毕竟我们不仅要获取当前最小数对,还得获取可能的下一个最小数对,即(i,j+1)(i+1,j)

 Queue<int[]> queue = new PriorityQueue<>((o1, o2) -> nums1[o1[0]] + nums2[o1[1]] - nums1[o2[0]] - nums2[o2[1]]);
 queue.add(new int[]{0, 0});

然后,就是不停地从队列中取出当前最小数对,并往队列里放入下一个个可能的最小数对

while (result.size() < k && !queue.isEmpty()) {
    int[] pair = queue.poll();
    int i = pair[0], j = pair[1];

    int p = i * 10000 + j;
    //已访问则跳过
    if (vis.contains(p)) {
        continue;
    }

    //添加当前对到结果中
    result.add(Arrays.asList(nums1[i], nums2[j]));
    //也标记为已访问
    vis.add(p);

    //当nums1未抵达边界时,提交nums2的当前数和nums1的下一个数
    if (i + 1 < nums1.length) {
        queue.add(new int[]{i + 1, j});
    }
    //当nums2未抵达边界时,提交nums1的当前数和nums2的下一个数
    if (j + 1 < nums2.length) {
        queue.add(new int[]{i, j + 1});
    }
}

循环的跳出条件则是达到K个数量,或是队列已空,即数组组成的数对不够K个时。

由于在添加次小数对时,是可能存在重复的,所以我还额外使用了一个Set来去重。里面存放了已经访问过了的下标对。
由于题目限制了nums1nums2的长度最大只有105,所以我将数对中nums1的下标i放大了10000倍,并和nums2的下标j相加,化二维为一维。

    Set<Integer> vis = new HashSet<>();

	……
	……
	
	int p = i * 10000 + j;
	//已访问则跳过
	if (vis.contains(p)) {
		continue;
	}
	
	//添加当前对到结果中
	result.add(Arrays.asList(nums1[i], nums2[j]));
	//也标记为已访问
	vis.add(p);

6、提交

在这里插入图片描述

7、咀嚼

如果是暴力的情况的话,我们会嵌套遍历nums1[]nums2[],时间复杂度将来到O(MN)Mnums1[]的长度Nnums2[]的长度。
而由于要存放所有产生的数对,所以空间复杂度同理也是O(MN)

而经过优化呢,我们此时的时间复杂度将为O(NlogN),而NK的大小。
当然,这里并不会辣么精准,你会发现优先级队列里存放的数据可能会超过K
而由于使用了一个Set和一个优先级队列,空间复杂度会来到O(2N),也是**O(N)**就是了。

8、学习大牛

嗯……
大牛到底是大牛,同是优先级队列,但使用思路上却是完全不同。

而且,除了堆的实现外,大牛还提供了了二分查找的实现方式,这里一并放出来供大家学习。
膜拜

9、总结

周五了,今天也顺利地抢到了回老家的票。
但是上海昨天却新增了5例本地病例,这让我们沪漂的社畜们很是担忧。

不知道这个年能否团聚呢……

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值