double 最大_LeetCode 1610—可见点的最大数目

关键字:计算几何,滑动窗口

归航return:LeetCode 460—LFU 缓存设计​zhuanlan.zhihu.com
zhihu-card-default.svg
归航return:(Trivial)LeetCode 1354——多次求和构造目标数组​zhuanlan.zhihu.com
f575ebad7cdb43708eb65febd36c2afa.png

Problem

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:

facdff6c38627174290361bee7c2168a.png
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:

529aa62fbdca26dc335b667b0c18b36e.png
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
1610. 可见点的最大数目 - 力扣(LeetCode)​leetcode-cn.com

Solution

这道题目饶有趣味。在 LeetCode 周赛中,这道题目的难度是 medium,但是在比赛结束之后数小时,难度被调整成了 hard 难度。这道题目是一个典型的计算几何问题,约束范围是

equation?tex=%5Cleft%5Bcur+-+%5Cfrac%7Bd%7D%7B2%7D%2C+cur+%2B+%5Cfrac%7Bd%7D%7B2%7D%5Cright%5D ,要求最大的覆盖个数。

首先,自然是要求出这个点的角度。根据高中的解析几何和三角函数知识,我们知道,这里实际上就是将极坐标和直角坐标进行互换的过程,也就是:

equation?tex=%5Cleft%5C%7B+%5Cbegin%7Baligned%7D+%26+x+%3D+%5Crho+%5Ccos%5Ctheta%5C%5C+%26+y+%3D+%5Crho+%5Csin%5Ctheta%5C%5C+%5Cend%7Baligned%7D+%5Cright.%5C%5C

其中

equation?tex=%5Crho 是到原点的长度(非负数),而
equation?tex=%5Ctheta 则是从
equation?tex=x 轴正半轴逆时针旋转的角度。一种特殊情况:当点位于原点的时候,
equation?tex=%5Crho+%3D+0 ,而
equation?tex=%5Ctheta 则是未定义的。这一点尤为重要,因为我们本题会涉及到这个问题。我们本题仅仅关心
equation?tex=%5Ctheta ,针对这个问题,
C++Java 均提供了库函数,称作 atan2(double y, double x) 或者 Math.atan2(double y, double x),得到的结果位于
equation?tex=%5Cleft%5B-%5Cpi%2C+%5Cpi%5Cright%5D 之间。当然,由于浮点运算的特性,这里一定存在误差。

相关的文档可以参考 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 and y are both zero, domain error does not occur
  • If x and y are both zero, range error does not occur either
  • If y is zero, pole error does not occur
  • If y is ±0 and x is negative or -0, ±π is returned
  • If y is ±0 and x 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 函数的值域是

equation?tex=%5Cleft%5B-%5Cpi%2C+%5Cpi%5Cright%5D ,而我们关心的是在单位圆上的角度的差,因此这里需要借用复数的幅角主值的概念来理解这个函数的结果。那么,怎么做到不漏掉角度差计算上大于阈值但实际上单位圆上的角度差小于阈值呢?我们想到了 LeetCode 503 的做法,就是多循环一次。由于圆的角度是
equation?tex=2%5Cpi 弧度,因此我们需要同时加入两个角度,一个是
atan2 的结果,一个是 atan2 + 2pi 的结果,这样就能保证不会漏掉了。

在这样加入完毕之后,对所有的角度进行排序,就转化成了很简单的最长子数组,且子数组的最大值减去最小值不超过预先给定的值的问题了,这个问题的解答也很简单,双指针即可。维护 [left, right] 的闭区间表示当前区间,如果符合要求就 ++right,否则 ++left,直到 right 超出合法的坐标范围为止。

最后别忘了,题目给的角度是角度值,需要乘以

equation?tex=%5Cfrac%7B%5Cpi%7D%7B180%7D 来得到弧度值,另外 C++ 没有提供
equation?tex=%5Cpi 的预先定义,因此需要使用一个函数来获得,方法也很简单:
equation?tex=%5Cpi+%3D+%5Carccos%5Cleft%28-1%5Cright%29 ,这是因为
equation?tex=%5Ccos%5Cleft%28%5Cpi%5Cright%29+%3D+-1 ,且
equation?tex=%5Cpi%5Cin%5Cleft%5B0%2C+%5Cpi%5Cright%5D 。最终代码如下:

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 是否超出下标范围。

时间复杂度:

equation?tex=O%5Cleft%28N%5Clog+N%5Cright%29 ,来源于排序;

空间复杂度:

equation?tex=O%5Cleft%28N%5Cright%29 ,来源于额外存放角度的数组。

EOF。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值