题目:
We have a list of points
on the plane. Find the K
closest points to the origin (0, 0)
.
(Here, the distance between two points on a plane is the Euclidean distance.)
You may return the answer in any order. The answer is guaranteed to be unique (except for the order that it is in.)
Example 1:
Input: points = [[1,3],[-2,2]], K = 1
Output: [[-2,2]]
Explanation:
The distance between (1, 3) and the origin is sqrt(10).
The distance between (-2, 2) and the origin is sqrt(8).
Since sqrt(8) < sqrt(10), (-2, 2) is closer to the origin.
We only want the closest K = 1 points from the origin, so the answer is just [[-2,2]].
Example 2:
Input: points = [[3,3],[5,-1],[-2,4]], K = 2
Output: [[3,3],[-2,4]]
(The answer [[-2,4],[3,3]] would also be accepted.)
Note:
1 <= K <= points.length <= 10000
-10000 < points[i][0] < 10000
-10000 < points[i][1] < 10000
这道题给了一个坐标的vector和一个数字k,要求离坐标原点最接近的k个坐标。这道题刚开始觉得很简单,不就计算一下距离然后对这个距离sort一下就完事儿了,后面写起代码才想起来我们最后还要保留这个点的原始坐标进行返回,而并不是只返回distance。于是我就开始想着,可以用一个map来存每个坐标和它对应的距离,然后对这个map进行sort。然而事实上可以直接重载sort时的comparator让它按照我们自定义的排序方式sort。但是,因为我们只需要前k个,所以对全部进行sort其实有时间上的浪费,于是可以使用quick select,它的想法和快排中的选pivot分两边有点像。在STL中vector的实现里也有,可以直接调用API:partial_sort或者n_element。两个的差别是对于partial_sort,vector.begin() + k,而nth_element需要vector.begin() + k - 1:
nth_element版本,208ms,92.45%,36.9M,87.5%:
class Solution {
public:
vector<vector<int>> kClosest(vector<vector<int>>& points, int K) {
nth_element(points.begin(), points.begin() + K - 1, points.end(), [](vector<int>& a, vector<int>& b) {
return pow(a[0], 2) + pow(a[1], 2) < pow(b[0], 2) + pow(b[1], 2);
});
return vector<vector<int>>(points.begin(), points.begin() + K);
}
};
partial_sort版本,272ms,45.89%,36.8M,96.88%:
class Solution {
public:
vector<vector<int>> kClosest(vector<vector<int>>& points, int K) {
partial_sort(points.begin(), points.begin() + K, points.end(), [](vector<int>& a, vector<int>& b) {
return pow(a[0], 2) + pow(a[1], 2) < pow(b[0], 2) + pow(b[1], 2);
});
return vector<vector<int>>(points.begin(), points.begin() + K);
}
};
这里的comparator采用的是<,对于vector来说,return a < b就会让排序从小到大排。
记笔记:(Reference:https://www.jianshu.com/p/e3f43ac87591)
nth_element
是把第n个元素放在第n个位置,不保证前面区间和后面区间的元素顺序,但是前面区间的元素会"小于等于"后面区间的元素。
partial_sort
是部分排序,对 [first, middle)内是顺序的,后面区间的顺序就不保证了
根据上面的笔记,因为nth_element不管两边的顺序只管放对边,所以自然也就运行效率更高一些。
也可以自己写一个quick select的算法,但是我懒,留到后面慢慢填坑吧,链接贴这儿了:https://leetcode.com/problems/k-closest-points-to-origin/discuss/221532/C%2B%2B-STL-quickselect-priority_queue-and-multiset
同时,也可以直接用priority queue来用最大堆和最小堆实现。我觉得最小堆比较简单,因为直接建好堆以后取前k个元素就完事儿了。看了discussion发现最大堆其实也可以做,我们维护一个size为k的最大堆,当size > k以后,每次push进去元素以后都把堆顶元素pop掉,保持size永远不会超过k,最后堆里的那k个就自然是最小的k个了。
采用最小堆的实现如下,276ms,43.62%,47.6M,48.44%:
class Solution {
public:
struct comparator {
bool operator() (vector<int>& a, vector<int>& b) {
return pow(a[0], 2) + pow(a[1], 2) > pow(b[0], 2) + pow(b[1], 2);
}
};
vector<vector<int>> kClosest(vector<vector<int>>& points, int K) {
priority_queue<vector<int>, vector<vector<int>>, comparator> pq(points.begin(), points.end());
/*for (int i = 0; i < points.size(); i++) {
pq.push(points[i]);
}*/
vector<vector<int>> result;
for (int i = 0; i < K; i++) {
result.push_back(pq.top());
pq.pop();
}
return result;
}
};
Java:学会了PriorityQueue的用lambda comparator initialize的方法。
Runtime: 21 ms, faster than 73.63% of Java online submissions for K Closest Points to Origin.
Memory Usage: 48.3 MB, less than 59.40% of Java online submissions for K Closest Points to Origin.
class Solution {
public int[][] kClosest(int[][] points, int K) {
PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> (
a[0] * a[0] + a[1] * a[1] - b[0] * b[0] - b[1] * b[1]));
for (int i = 0; i < points.length; i++) {
minHeap.add(points[i]);
}
int[][] result = new int[K][2];
for (int i = 0; i < K; i++) {
result[i] = minHeap.poll();
}
return result;
}
}
采用最大堆的话就不能直接从vector新建priority_queue了,而要采用一个个push的形式,最后返回结果也是一样要遍历,不懂这种做法到底有啥好的,运行时间316 ms,27.89%,空间48.7 MB,37.50%,时空效率也很低啊QAQ:
class Solution {
public:
struct comparator {
bool operator() (vector<int>& a, vector<int>& b) {
return pow(a[0], 2) + pow(a[1], 2) < pow(b[0], 2) + pow(b[1], 2);
}
};
vector<vector<int>> kClosest(vector<vector<int>>& points, int K) {
priority_queue<vector<int>, vector<vector<int>>, comparator> pq;
for (int i = 0; i < points.size(); i++) {
pq.push(points[i]);
if (pq.size() > K) {
pq.pop();
}
}
vector<vector<int>> result;
for (int i = 0; i < K; i++) {
result.push_back(pq.top());
pq.pop();
}
return result;
}
};
Java:忘了可以直接在push的时候先push然后再判断size是否>K,>K就pop。
Runtime: 30 ms, faster than 53.03% of Java online submissions for K Closest Points to Origin.
Memory Usage: 47.7 MB, less than 92.93% of Java online submissions for K Closest Points to Origin.
class Solution {
public int[][] kClosest(int[][] points, int K) {
PriorityQueue<int[]> maxHeap = new PriorityQueue<>((a, b) -> -(
a[0] * a[0] + a[1] * a[1] - b[0] * b[0] - b[1] * b[1]));
for (int i = 0; i < points.length; i++) {
maxHeap.add(points[i]);
if (maxHeap.size() > K) {
maxHeap.poll();
}
}
int[][] result = new int[K][2];
for (int i = 0; i < K; i++) {
result[i] = maxHeap.poll();
}
return result;
}
}