51-TOP-K问题练习-LeetCode373查找和最小的k对数字

题目

给定两个以升序排列的整数数组 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]

示例 3:

输入: nums1 = [1,2], nums2 = [3], k = 3
输出: [1,3],[2,3]
解释: 也可能序列中所有的数对都被返回:[1,3],[2,3]

提示:

    1 <= nums1.length, nums2.length <= 10^5
    -10^9 <= nums1[i], nums2[i] <= 10^9
    nums1 和 nums2 均为升序排列
    1 <= k <= 1000


思路

先将所有可能的组合对(u,v)筛选出来。外层循环取u——第一个元素;内层循环取v——第二个元素。

用一个大小为k的最大堆存储组合对,即优先级队列中存储类的对象(比存数组好用)。

k的大小和数组长度没有直接关系,需要判断边界。

示例 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]

此时k == nums1.length || nums2.length,当然要把整个数组遍历完。在n个升序元素中,取前n个元素,当然要整个遍历。

示例 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 < nums1.length || nums2.length,遍历数组只需走k步即可。要从n个升序数组中,取前k个最小值,只需要从第一个元素开始走k步即可。

此处的数组遍历是双重循环,u-来自于nums1,v-来自于nums2。要把nums1和nums2都走一遍。

nums1 = [1,1,2]:u,k < nums1.length,因此2这个值肯定用不到,最多走到第2个1就结束。

nums2 = [1,2,3]:v,k < nums2.length,因此3这个值肯定用不到,最多走到第2个2就结束。

拿着nums1数组中的第一个1去遍历nums2数组中的[1,2],得到[1,1],[1,2]。

拿着nums1数组中的第二个1去遍历nums2数组中的[1,2],得到[1,1],[1,2]。

在最终得到的[1,1],[1,2],[1,1],[1,2]四个集合中选出前2个和最小的组合,得到[1,1],[1,1]。

示例 3:

输入: nums1 = [1,2], nums2 = [3], k = 3
输出: [1,3],[2,3]
解释: 也可能序列中所有的数对都被返回:[1,3],[2,3]

此时k > nums1.length || nums2.length,排列组合的个数

总结:

以上三种情况,我们在遍历数组时不是直接遍历结束,而是走Math.min(k, nums.length)。


代码

class Solution {
    //此时Pair类就具备可比较能力
    private class Pair implements Comparable {
        //第一个数组元素
        int u;
        //第二个数组元素
        int v;

        //构造方法
        public Pair(int u, int v) {
            this.u = u;
            this.v = v;
        }

        @Override
        public int compareTo(Pair o) {
            //取小用大,将JDK内置的最小堆转化为最大堆
            return (o.u + o.v) - (this.u + this.v);
        }
    }

    public List> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        Queue queue = new PriorityQueue<>();

        //最外层取u值
        for (int i = 0; i < Math.min(nums1.length, k); i++) {
            //最内层取v值
            for (int j = 0; j < Math.min(nums2.length, k); j++) {
                if(queue.size() < k) {
                    queue.offer(new Pair(nums1[i], nums2[j]));
                } else {
                    Pair pair = queue.peek();
                    if (nums1[i] + nums2[j] < (pair.u + pair.v)) {
                        queue.poll();
                        queue.offer(new Pair(nums1[i], nums2[j]));
                    }
                }
            }
        }

        //队列中就存储了前k个最小的pair对象
        //List套List,二维数组
        List> ret = new ArrayList<>();
        //边界条件 k > queue.size();
        for (int i = 0; i < k && !(queue.isEmpty()); i++) {
            List temp = new ArrayList<>();
            Pair pair = queue.poll();
            temp.add(pair.u);
            temp.add(pair.v);
            ret.add(temp);
        }
        return ret;
    }
}

承上启下:

  • 二叉树:理论基础,无实际用处,做个题。
  • 二分搜索树:TreeSet、TreeMap、红黑树、二分平衡搜索树。(在Java标准库中)
  • 哈希表:HashSet、HashMap。(在Java标准库中)
  1. Set:存储不重复元素的线性表。若只是判定元素是否存在,或者过滤重复元素,使用Set集合。
  2. Map:存储key-value键值对。存储的数据是一种映射关系,需要根据不重复的key对应value,则需要使用Map集合。(例:身份证号=姓名)
  • Set和Map是一种专门用来进行搜索的容器/数据结构,其搜素的效率与其具体的实例化子类有关。
  • 尽量不要用Map和Set集合进行遍历,对于这俩集合的遍历操作,是效率比较低的操作。使用Set和Map集合最核心的操作是搜索。

你可能感兴趣的:(Java数据结构,数据结构,java)