【堆】B007_查找和最小的 K 对数字(最小堆 | 剪枝 | 双剪枝)

一、题目描述

You are given two integer arrays nums1 and nums2 sorted in ascending order and an integer k.

Define a pair (u,v) which consists of one element from the first array and one element from the second array.

Find the k pairs (u1,v1),(u2,v2) ...(uk,vk) with the smallest sums.

Input: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
Output: [[1,2],[1,4],[1,6]] 

Explanation: The first 3 pairs are returned from the sequence: 
             [1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]

二、题解

private int twoSum(int[] arr) {
  return arr[0] + arr[1];
}

方法一:最小堆 + 穷举

算法

最普遍的做法就是组合 nums1 与 nums2 的所有数字;然后,将所有组合按照组合数之和降序存放在队列中,形成一个最小堆;最后将前 k 项组合添加进列表中。

/**
 * @date: 2/21/2020 8:41 PM
 * @Execution info:296ms,5.07%,29.84%
 */
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {

  LinkedList<List<Integer>> resList = new LinkedList<>();
  PriorityQueue<int[]> pQ = new PriorityQueue<>((e1, e2) -> twoSum(e2)-twoSum(e1));

  for (int n1 : nums1)
  for (int n2 : nums2) {
    int[] arr = new int[] {n1, n2};
    pQ.add(arr);
    if (pQ.size() > k)
      pQ.poll();
  }

  while (!pQ.isEmpty()) {
    LinkedList<Integer> list = new LinkedList<>();
    int[] arr = pQ.poll();
    list.add(arr[0]); list.add(arr[1]);
    resList.add(list);
  }
  return resList;
}

复杂度分析

  • 时间复杂度: O(m × n × k logk),m 为 nums1 的元素个数,n 为 nums2 的元素个数,建立最小堆需要 k logk 时间。
  • 空间复杂度: O ( k ) O(k)

方法二:最小堆 + 剪枝

对当队列已经足够 k 个时,如果有新的数字对要进队,应该先与队头元素进行比较,如果不小于队头元素的和,就没必要进队;否则,将队头元素出队,将这地数入队。

/**
 * @date: 2/21/2020 8:45 PM
 * @Execution info:184ms,8.07%,31.42%
 */
public List<List<Integer>> kSmallestPairs1(int[] nums1, int[] nums2, int k) {

  LinkedList<List<Integer>> resList = new LinkedList<>();
  PriorityQueue<int[]> pQ = new PriorityQueue<>((e1, e2) -> twoSum(e2)-twoSum(e1));

  for (int n1 : nums1)
   for (int n2 : nums2) {
     int[] arr = new int[] {n1, n2};
     if (pQ.size() < k)
       pQ.add(arr);
     else if (pQ.size() >= k  && twoSum(arr) < twoSum(pQ.peek())) {
       pQ.poll();
       pQ.add(arr);
     }
   }

  while (!pQ.isEmpty()) {
    LinkedList<Integer> list = new LinkedList<>();
    int[] arr = pQ.poll();
    list.add(arr[0]);
    list.add(arr[1]);
    resList.add(list);
  }
  return resList;
}

复杂度分析

  • 时间复杂度: O(m × n × k logk),m 为 nums1 的元素个数,n 为 nums2 的元素个数,建立最小堆需要 k logk 时间。
  • 空间复杂度: O ( k ) O(k)

方法三:最小堆 + 双剪枝

这里在继承方法二的剪枝的前提下,继续优化代码:

由于数组 nums1,nums2 都是有序的,又因为某一对值 (u, v) 中的 u 一定来自 nums1,v 一定来自 nums2。

我们可以这样做,当前 n1,n2 均大于上一次(queue 满 k 个之后)的添加数字,直接退出本次内层循环。因为队列满了,就证明我已经拿够了 k 对和最小的数字了,没必要再添加更大的。

/**
 * @date: 2/21/2020 8:54 PM
 * @Execution info:24ms,58.35%,31.74%
 */
public List<List<Integer>> kSmallestPairs2(int[] nums1, int[] nums2, int k) {
  ArrayList<List<Integer>> resList = new ArrayList<>();
  PriorityQueue<int[]> pQ = new PriorityQueue<>((e1, e2) -> twoSum(e2)-twoSum(e1));
  int pre_n1 = 0x7fffffff;
  int pre_n2 = 0x7fffffff;

  for (int n1 : nums1)
  for (int n2 : nums2) {
    if (n1 > pre_n1 && n2 > pre_n2) {
      break;
    }
    int[] arr = new int[] {n1, n2};
    if (pQ.size() < k) {
      pQ.add(arr);
    } else if (pQ.size() >= k && twoSum(arr) < twoSum(pQ.peek())) {
        pQ.poll();
        pQ.add(arr);
        pre_n1 = n1;
        pre_n2 = n2;
      }
  }

  while (!pQ.isEmpty()) {
    LinkedList<Integer> list = new LinkedList<>();
    int[] arr = pQ.poll();
    list.add(arr[0]);
    list.add(arr[1]);
    resList.add(list);
  }
  return resList;
}

复杂度分析

  • 时间复杂度: O(m × n × k logk),m 为 nums1 的元素个数,n 为 nums2 的元素个数,建立最小堆需要 k logk 时间。
  • 空间复杂度: O ( k ) O(k)
发布了419 篇原创文章 · 获赞 94 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_43539599/article/details/104435303