[Java][LC973] 经典top k问题的三种解决方法

翻译自:https://leetcode.com/problems/k-closest-points-to-origin/discuss/220235/Java-Three-solutions-to-this-classical-K-th-problem.

求解无序数组的第k大(小)元素是一个非常经典的问题。
在这里我想以leetcode 973(离原点第k近的点) 为例,总结和分享top k问题常见的几种解题思路:

  1. 最简单的方法是对所有元素进行排序。我们可以使用java提供的sort方法,使代码变得简洁明了。
    优点:代码简短,直观,易于理解
    缺点:效率低,且需要事先知道所有元素,也就是说,这不是一个实时的解决办法。
    理论上,这种方法的时间复杂度是O(nlogn)
public int[][] kClosest(int[][] points, int K) {
    Arrays.sort(points, (p1, p2) -> p1[0] * p1[0] + p1[1] * p1[1] - p2[0] * p2[0] - p2[1] * p2[1]);
    return Arrays.copyOfRange(points, 0, K);
}
  1. 第二种方法是对第一种方法的改进,我们不再需要对所有元素进行排序。
    相反的,我们维护一个容量为k的最大堆,并将元素一个个地放入堆中。如果堆的大小超过了k,我们就从堆中移走一个元素,使得堆的大小始终为k。例如,如果我们要找第k小的元素,当堆的大小超过k时,我们将堆里最大的元素移出,因为显然它不具有成为第k小的资格。
    优点:这是一个实时的解决办法,我们并不需要事先知道所有元素
    缺点:这仍然不是效率最高的解决办法
    理论上,它的时间复杂度是O(nlogk)
public int[][] kClosest(int[][] points, int K) {
    PriorityQueue<int[]> pq = new PriorityQueue<int[]>((p1, p2) -> p2[0] * p2[0] + p2[1] * p2[1] - p1[0] * p1[0] - p1[1] * p1[1]);
    for (int[] p : points) {
        pq.offer(p);
        if (pq.size() > K) {
            pq.poll();
        }
    }
    int[][] res = new int[K][2];
    while (K > 0) {
        res[--K] = pq.poll();
    }
    return res;
}
  1. 最后一种方法基于快速排序,我们也可以叫它快速选择。在快排中,我们始终需要选择一个基准元素(pivot),用它和其他元素比较。在一次迭代后,我们可以得到一个数列,所有比基准值小的元素将放在它左边,而比基准值大的元素将放在它的右边(假设升序排序)。受此启发,每次迭代中,我们都找到基准值应该被放在的位置p。接着我们比较位置p和k以决定在下个迭代中,我们处理基准值左侧或右侧的值,直到p=k。
    优点:(平均)效率高
    缺点:这种方法并非实时,效率也不稳定。
    理论上,这种方法的平均时间复杂度为O(n),但最坏情况时间复杂度可为O(n^2).
public int[][] kClosest(int[][] points, int K) {
    int len =  points.length, l = 0, r = len - 1;
    while (l <= r) {
        int mid = helper(points, l, r);
        if (mid == K) break;
        if (mid < K) {
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    return Arrays.copyOfRange(points, 0, K);
}

private int helper(int[][] A, int l, int r) {
    int[] pivot = A[l];
    while (l < r) {
        while (l < r && compare(A[r], pivot) >= 0) r--;
        A[l] = A[r];
        while (l < r && compare(A[l], pivot) <= 0) l++;
        A[r] = A[l];
    }
    A[l] = pivot;
    return l;
}

private int compare(int[] p1, int[] p2) {
    return p1[0] * p1[0] + p1[1] * p1[1] - p2[0] * p2[0] - p2[1] * p2[1];
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值