介绍
标签:堆、排序、分治算法
建议使用分治算法来解决
leetcode973 - 最接近原点的 K 个点
难度 中等
最接近原点的 K 个点
https://leetcode-cn.com/problems/k-closest-points-to-origin/
题目:
我们有一个由平面上的点组成的列表 points。需要从中找出 K 个距离原点 (0, 0) 最近的点。
(这里,平面上两点之间的距离是欧几里德距离。)
你可以按任何顺序返回答案。除了点坐标的顺序之外,答案确保是唯一的。
示例 1:
输入:points = [[1,3],[-2,2]], K = 1
输出:[[-2,2]]
解释:
(1, 3) 和原点之间的距离为 sqrt(10),
(-2, 2) 和原点之间的距离为 sqrt(8),
由于 sqrt(8) < sqrt(10),(-2, 2) 离原点更近。
我们只需要距离原点最近的 K = 1 个点,所以答案就是 [[-2,2]]。
示例 2:
输入:points = [[3,3],[5,-1],[-2,4]], K = 2
输出:[[3,3],[-2,4]]
(答案 [[-2,4],[3,3]] 也会被接受。)
提示:
- 1 <= K <= points.length <= 10000
- -10000 < points[i][0] < 10000
- -10000 < points[i][1] < 10000
解题思路
最开始的想法就是调用sort方法来解决,于是就有了前面四个版本的代码
- 第一版代码是最普通的重写compare的排序方法,而且没考虑任何优化,所以也就是最慢的
- 第二版代码是稍加优化的版本,时间上快了7ms
- 第三版代码用到了Comparator.comparingInt与lamda表达式,更加简便也更快
- 第四版代码在第三版的基础上再次完善lamda表达式
- 第五版也就是最终版,自己手写快排,因为自己写的快排,那么跳出的条件就是只用把K位之前的全部排序完成就好了,可以省下不少时间,详解解释看代码中的注释
- 还有用优先队列也就是大根堆的解决的,但是也是调用的内置函数,是后来写的,放最后了。
结果展示
时间效率与空间效率的对比如下:
后提交的官方那个,先提交那个是我改了的
代码
第1版
class Solution {
public int[][] kClosest(int[][] points, int K) {
Arrays.sort(points, new Comparator<int[]>() {
public int compare(int[] x, int[] y) {
int a = x[0] * x[0] + x[1] * x[1];
int b = y[0] * y[0] + y[1] * y[1];
return a - b;
}
});
return Arrays.copyOfRange(points, 0, K);
}
}
第2版
class Solution {
public int[][] kClosest(int[][] points, int K) {
Arrays.sort(points, new Comparator<int[]>() {
public int compare(int[] x, int[] y) {
return (x[0] * x[0] + x[1] * x[1]) - (y[0] * y[0] + y[1] * y[1]);
}
});
return Arrays.copyOfRange(points, 0, K);
}
}
第3版
class Solution {
public int[][] kClosest(int[][] points, int K) {
//这个方法没学明白,有空看源码
Arrays.sort(points, Comparator.comparingInt(
(int[] point) -> (point[0] * point[0] + point[1] * point[1])
));
return Arrays.copyOfRange(points, 0, K);
}
}
第4版
class Solution {
public int[][] kClosest(int[][] points, int K) {
Arrays.sort(points, (a, b) -> ((a[0]*a[0]+a[1]*a[1]) - (b[0]*b[0]+b[1]*b[1])));
return Arrays.copyOfRange(points, 0, K);
}
}
第5版
//手写快排
class Solution {
public int[][] kClosest(int[][] points, int K) {
int len = points.length;
//初始化左右两端
int left = 0;
int right = points.length - 1;
int index = - 1;
//只有当index在K-1位的时候,才说明把前K个点排好序了
while (index != K - 1) {
index = partition(points, left, right);
if (index < K - 1) {
left = index + 1;
} else if (index > K - 1) {
right = index - 1;
}
}
//返回数组中0-k的点
return Arrays.copyOfRange(points, 0, K);
}
/*
对从left到right排序,仅确保在j之前的距离都比j小
*/
private int partition(int[][] points, int left, int right) {
int j = left;
//取最后一个作为标识长度
int tmp = getDis(points[right][0], points[right][1]);
for (int i = left; i < right; ++i) {
//把所有比tmp放到前面去
if (getDis(points[i][0], points[i][1]) <= tmp) {
swap(points, j++, i);
}
}
swap(points, j, right);
return j;
}
/*
交换元素
*/
private void swap(int[][] points, int a, int b) {
int[] t = points[a];
points[a] = points[b];
points[b] = t;
}
/*
求距离
*/
public int getDis(int x, int y) {
return x * x + y * y;
}
}
第6版
class Solution {
public int[][] kClosest(int[][] points, int K) {
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] array1, int[] array2) {
return array1[0] - array2[0];
}
});
//直接全部进去队列,官方那个还要在后面再次遍历,反而慢些
for (int i = 0; i < points.length; i++) {
pq.offer(new int[]{points[i][0] * points[i][0] + points[i][1] * points[i][1], i});
}
int[][] ans = new int[K][2];
for (int i = 0; i < K; ++i) {
ans[i] = points[pq.poll()[1]];
}
return ans;
}
}