关键字:计算几何,滑动窗口
归航return:LeetCode 460—LFU 缓存设计zhuanlan.zhihu.comProblem
You are given an array points
, an integer angle
, and your location
, where location = [posx, posy]
and points[i] = [xi, yi]
both denote integral coordinates on the X-Y plane.
Initially, you are facing directly east from your position. You cannot move from your position, but you can rotate. In other words, posx
and posy
cannot be changed. Your field of view in degrees is represented by angle
, determining how wide you can see from any given view direction. Let d
be the amount in degrees that you rotate counterclockwise. Then, your field of view is the inclusive range of angles [d - angle/2, d + angle/2]
.
You can see some set of points if, for each point, the angle formed by the point, your position, and the immediate east direction from your position is in your field of view.
There can be multiple points at one coordinate. There may be points at your location, and you can always see these points regardless of your rotation. Points do not obstruct your vision to other points.
Return the maximum number of points you can see.
Example 1:
Input: points = [[2,1],[2,2],[3,3]], angle = 90, location = [1,1]
Output: 3
Explanation: The shaded region represents your field of view. All points can be made visible in your field of view, including [3,3] even though [2,2] is in front and in the same line of sight.
Example 2:
Input: points = [[2,1],[2,2],[3,4],[1,1]], angle = 90, location = [1,1]
Output: 4
Explanation: All points can be made visible in your field of view, including the one at your location.
Example 3:
Input: points = [[1,0],[2,1]], angle = 13, location = [1,1]
Output: 1
Explanation: You can only see one of the two points, as shown above.
Constraints:
1 <= points.length <= 10**5
points[i].length == 2
location.length == 2
0 <= angle < 360
0 <= posx, posy, xi, yi <= 10**9
Solution
这道题目饶有趣味。在 LeetCode 周赛中,这道题目的难度是 medium
,但是在比赛结束之后数小时,难度被调整成了 hard
难度。这道题目是一个典型的计算几何问题,约束范围是
首先,自然是要求出这个点的角度。根据高中的解析几何和三角函数知识,我们知道,这里实际上就是将极坐标和直角坐标进行互换的过程,也就是:
其中
C++
和
Java
均提供了库函数,称作
atan2(double y, double x)
或者
Math.atan2(double y, double x)
,得到的结果位于
相关的文档可以参考 C 的这里 C++ 的这里 Java 的这里 和 Python 的这里。由于我们这里的坐标差都是有限的,因此唯一的 edge case
就是两者都是 0。在这种情况下,由于我们的 0 的得到的过程是两个 int
相减然后转换成 double
型,因此这里的浮点数一定是 atan2(+0.0, +0.0)
。针对此,C++
的结果是多种可能(即未定义),而 Java
则是规定是 +0.0. Python
的文档中没有明确指出特殊情况的定义,但由于 Python
的底层是 C 语言,因此猜测就是按照上述 C 语言的文档进行特殊情况的结果。
- If
x
andy
are both zero, domain error does not occur - If
x
andy
are both zero, range error does not occur either - If
y
is zero, pole error does not occur - If
y
is±0
andx
is negative or-0
,±π
is returned - If
y
is±0
andx
is positive or+0
,±0
is returned
Cppreference(C++)
- If the first argument is positive zero and the second argument is positive, or the first argument is positive and finite and the second argument is positive infinity, then the result is positive zero.
Oracle Java Docs(Java)
然而,我们自己一定能看到自己,因此这种情况一定能看到,这是一种边界情况。
另外,值得注意的是,由于 atan2
函数的值域是
atan2
的结果,一个是
atan2 + 2pi
的结果,这样就能保证不会漏掉了。
在这样加入完毕之后,对所有的角度进行排序,就转化成了很简单的最长子数组,且子数组的最大值减去最小值不超过预先给定的值的问题了,这个问题的解答也很简单,双指针即可。维护 [left, right]
的闭区间表示当前区间,如果符合要求就 ++right
,否则 ++left
,直到 right
超出合法的坐标范围为止。
最后别忘了,题目给的角度是角度值,需要乘以
Java:
class Solution {
public int visiblePoints(List<List<Integer>> points, int angle, List<Integer> location) {
final int pointCnt = points.size();
List<Double> angles = new ArrayList<>();
int extraAns = 0;
for (int i = 0; i < pointCnt; ++i){
int deltaY = points.get(i).get(1) - location.get(1);
int deltaX = points.get(i).get(0) - location.get(0);
if (deltaY == 0 && deltaX == 0){
++extraAns;
continue;
}
double curAngle = Math.atan2(deltaY, deltaX);
angles.add(curAngle);
angles.add(curAngle + 2 * Math.PI);
}
Collections.sort(angles);
int ans = 0;
int left = 0;
int right = 0;
double maxAngle = angle * Math.PI / 180.0;
while (true){
if (right == angles.size()){
return ans + extraAns;
}
if (angles.get(right) - angles.get(left) <= maxAngle){
ans = Math.max(ans, right - left + 1);
++right;
}
else{
++left;
}
}
}
}
C++ :
class Solution {
public:
int visiblePoints(vector<vector<int>>& points, int angle, vector<int>& location) {
int extraCnt = 0;
vector<double> angles;
const double PI = acos(-1);
for (const vector<int>& point : points){
int deltaY = point[1] - location[1];
int deltaX = point[0] - location[0];
if (deltaX == 0 && deltaY == 0){
++extraCnt;
continue;
}
double curAngle = atan2(deltaY, deltaX);
angles.emplace_back(curAngle);
angles.emplace_back(curAngle + 2 * PI);
}
sort(angles.begin(), angles.end());
double threshold = angle * PI / 180;
int left = 0;
int right = 0;
int ans = 0;
while (true){
if (right == angles.size()){
return ans + extraCnt;
}
if (angles[right] - angles[left] <= threshold){
ans = max(ans, right - left + 1);
++right;
}
else{
++left;
}
}
}
};
C:
int compare(const void* x, const void* y){
double a = *(double*)x;
double b = *(double*)y;
return (b < a) - (a < b);
}
int visiblePoints(int** points, int pointsSize, int* pointsColSize, int angle, int* location, int locationSize){
double angles[2 * pointsSize];
const double PI = acos(-1);
int angleCnt = 0;
int extraCnt = 0;
for (int i = 0; i < pointsSize; ++i){
int deltaY = points[i][1] - location[1];
int deltaX = points[i][0] - location[0];
if (deltaX == 0 && deltaY == 0){
++extraCnt;
continue;
}
double curAngle = atan2(deltaY, deltaX);
angles[angleCnt++] = curAngle;
angles[angleCnt++] = curAngle + 2 * PI;
}
qsort(angles, angleCnt, sizeof(double), compare);
int left = 0;
int right = 0;
int ans = 0;
double threshold = angle * PI / 180;
while (1){
if (right == angleCnt){
return ans + extraCnt;
}
if (angles[right] - angles[left] <= threshold){
int tmp = right - left + 1;
ans = tmp > ans ? tmp : ans;
++right;
}
else{
++left;
}
}
}
Python3:
import math
class Solution:
def visiblePoints(self, points: List[List[int]], angle: int, location: List[int]) -> int:
angles = []
extraAns = 0
for i in range(len(points)):
deltaY = points[i][1] - location[1]
deltaX = points[i][0] - location[0]
if not deltaY and not deltaX:
extraAns += 1
continue
curAngle = math.atan2(deltaY, deltaX)
angles.append(curAngle)
angles.append(curAngle + 2 * math.pi)
angles.sort()
threshold = angle * math.pi / 180
left = 0
right = 0
ans = 0
while True:
if right == len(angles):
return ans + extraAns
if angles[right] - angles[left] <= threshold:
ans = max(ans, right - left + 1)
right += 1
else:
left += 1
这道题目我总共错了两次,第一个错误就是没有考虑到自摸的情况,第二个则是没有考虑到循环超出 atan2
范围的情况。特别注意,这道题目并不能在阈值上加上一个小常数,这会导致在 108/109 个 testcase 上出错。然后在我撰写题解的时候,LeetCode 更新了测试用例,加上了三个全部自摸的 test case,这个时候上述的 angles
数组就是空的了,因此这个时候必须直接首先判断 right
是否超出下标范围。
时间复杂度:
空间复杂度:
EOF。