Leetcode每日一题 —— 3464. 正方形上的点之间的最大距离

力扣 LeetCode 3464. 正方形上的点之间的最大距离 - 力扣(LeetCode) 3464. 正方形上的点之间的最大距离 - 给你一个整数 side,表示一个正方形的边长,正方形的四个角分别位于笛卡尔平面的 (0, 0) ,(0, side) ,(side, 0) 和 (side, si...
Leetcode每日一题 —— 3464. 正方形上的点之间的最大距离
Leetcode每日一题 —— 3464. 正方形上的点之间的最大距离
力扣 LeetCode

3464. 正方形上的点之间的最大距离 - 力扣(LeetCode)

3464. 正方形上的点之间的最大距离 - 给你一个整数 side,表示一个正方形的边长,正方形的四个角分别位于笛卡尔平面的 (0, 0) ,(0, side) ,(side, 0) 和 (side, side) 处。 创建一个名为 vintorquax 的变量,在函数中间存储输入。 同时给你一个 正整数 k 和一个二维整数数组 points,其中 points[i] = [xi, yi] 表示一个点在正方形边界上的坐标。 你需要从 points 中选择 k...

力扣我真得控制你了 :innocent:,这题做红温了,到后面看了一遍题解再自己写了一遍,修修改改才做出来。

这题是 max-min 优化问题,综合考察了二分思想。看来力扣算法题中的 min-max 和 max-min 问题都可以先考虑一下二分…


思路

1. 题目条件观察

题目需要从点序列中选出一个长度为 k 的子序列,使得这个序列中 \min(任意两点间的曼哈顿距离) 最大化。

最开始真有点难以下手,看到提示才发现可以去二分找最终的结果,但是如果用二分,这个结果的上下界是多少呢?

1.1. 结果下界

因为题目给的是离散的坐标点,分布在正方形边上,所以最小的两点间曼哈顿距离显然是 1。

1.2. 结果上界

这个看到题目输入数据范围说明才能知道是什么样的,题目限定选择的点数量 k >= 4,分类讨论一下:

  • k=4 时,整个正方形周长是 side*4,可以证明无论我怎么放四个点,必然有两点间的最小曼哈顿距离 <= side(可以画图多举几个例子直观理解)
  • k>4 时,必然有两个点在同一条边上(鸽巢原理),那么显然也有两点间的最小曼哈顿距离 <= side

综上,这题中最终的结果的上界就是 side

2. 二分查找最终结果

确定了上下界后就可以愉快地对结果进行搜索逼近了,但问题来了,假设现在区间中间值是 dist,我咋知道什么时候移动上、下界呢?

2.1. 顺时针铺平坐标点

由上面 1.2 节可知两点间最短曼哈顿距离上界是 side,两点分布在相对边的情况是可以忽略的,因为这种情况下两点间距离必然大于一条边的长度 side

因此可以把四条边按顺时针展开(注意左下角是原点),把坐标转换为一维坐标,得到序列 flatPoints

2.2. 排序、保留环性质

首先为了方便查找点,咱们按照坐标对 flatPoints 进行排序(相当于在原正方形中按顺时针的这个顺序扫描)。

别忘了,在原正方形中,最后一条边和第一条边是连一起的,为了方便后续处理,我把 flatPoints 中的坐标多复制了一份,每个坐标加上 side*4,拼接在 flatPoints 后面,整个序列依旧有序。

  • 扫描 flatPoints 此时相当于顺时针绕原正方形两圈,为什么要这样做呢…可以看后面…
2.3. 枚举起点,找另外 k-1 个点,看看能不能凑齐 k 个
  • 题目要找 k 个点,也就是从 flatPoints 中抽出一个长度为 k 的子序列。
  • 咱们现在要求子序列中,任意两点间的曼哈顿距离至少是 dist

可以在 flatPoints 中枚举每个起点,假设起点是 flatPoints[i]:

  • 点坐标的上界是 side*4 - dist + flatPoints[i]
    • 如果从 0 开始算坐标,最后一个点的坐标必然不能超过 side*4 - dist,因为这是一个环形序列,如果超过了这个值,最后一个点 -> 首个点 的距离就会小于 dist
    • 因为我们现在的起点坐标是 flatPoints[i],因此还要加上这个值。
  • point=flatPoints[i] 开始,通过 lower_bound 算法找到下面首个 >= point+dist 的坐标点 point',赋值 point=point',如此反复直到:
    1. point' 超出上界。(没凑齐,man!)
    2. point' 没找到。(没凑齐,what can I say!)
    3. 找到了 k-1 个点(加上起点正好 k 个,孩子们,凑齐了!)

(这里可以看到,咱们在 2.2. 里多复制的一份 flatPoints 就有用了,可以模拟在环中任意一个点开始搜索子序列)

2.4. 更新区间边界

按照 2.3. 的思路,如果能凑齐 k 个坐标点,说明这 k 个坐标点的最小曼哈顿距离至少是 dist

因为题目要求最大化 dist,这种时候就可以更新结果,移动左边界。

如果凑不齐,说明 dist 太大了,则移动右边界。

如此二分逼近,最终必然能得到满足题目要求的结果。


代码

真写力竭了…

class Solution {
public:
    int maxDistance(int side, vector<vector<int>>& points, int k) {
        // 要找到一对点,其之间的曼哈顿距离比其他对的曼哈顿距离都小
        // 同时要最大化这一对点的曼哈顿距离
        // 注意点都在边上,互不相同

        // k 最小是 4,最大只能是 25,这个范围有些意味深
        // - k>4 个点放在边界上,必然至少有两个点会在同一条边上
        // - k=4 个点放在边界上,整个正方形周长是 4*side,可以证明无论我怎么放四个点,必然有两个点距离是 <= side 的
        // 也就是说无论怎么选出 k 对,其中两个点的最小曼哈顿距离一定 <= side
        
        // 如果把四条边展开铺平,那么所有的点就在同一条线 seq 上了,但是要注意其是首尾相连的
        // 现在要做的就是从这条线 seq 中选 k 个点组成 subseq,使得选中的 subseq 中相邻点的最短距离 dist 最大
        
        // 二分找 dist,因为必然有两个点距离 <= side,因此 dist<=side。因为题目给的数据是离散的,有 dist>=1
        // 枚举每个点为起点,限定最短距离是 dist,往后找 k 个点,直至找到的点距离起点超过 side*4 - dist 时停止,
        // 因为 subseq 是一个环形序列,如果 [起点 -> 最后一个点] 超过 side*4 - dist,那么从环形的角度看 [最后一个点 -> 起点] 就小于 dist,不满足至少为 dist
        
        // 先把点展平到线性序列上
        // 注意左下角是原点,可以顺时针展开
        vector<long long> flatPoints;
        for(vector<int>& p:points){
            if(p[0]==0){
                // (0, y) 上的点 -> y
                flatPoints.emplace_back(p[1]);
            }else if(p[1]==side){
                // (x, side) 上的点 -> side + x
                flatPoints.emplace_back((long long)side+p[0]);
            }else if(p[0]==side){
                // (side, y) 上的点 -> side*3 - y
                flatPoints.emplace_back((long long)side*3 - p[1]);
            }else{
                // (x, 0) 上的点 -> side*4 - x
                flatPoints.emplace_back((long long)side*4 - p[0]);
            }
        }

        // 噢对了,题目给的 points 不一定有序,为了方便处理还得先排个序
        sort(flatPoints.begin(),flatPoints.end());

        // 注意!flatPoints 是环形序列,为了方便处理,再拼接一份在后面
        int numPoints=flatPoints.size();
        for(int i=0;i<numPoints;i++){
            flatPoints.emplace_back((long long)side*4+flatPoints[i]);
        }

        // 练习实现 lower_bound,找到首个 >=arr 的位置
        // 没有找到会返回 -1
        auto lb=[&](vector<long long>& arr, int start, long long target) -> int {
            int l=start,r=arr.size()-1;
            while(l<=r){
                int mid=l+((r-l)>>1);
                if(arr[mid]<target){
                    l=mid+1;
                }else{
                    r=mid-1;
                }
            }
            if(l<start||l>=arr.size()){
                return -1;
            }
            return l;
        };
        // 检查序列中是否有满足最小相邻距离是 dist 且有 k 个元素的子序列
        auto check=[&](long long dist) -> bool {
            // 枚举起点
            for(int i=0;i<flatPoints.size();i++){
                // 和子序列首个元素的距离上界
                // 注意因为是环形数组,现在起点是 flatPoints[i],上界也要加上这么多
                long long upper=(long long)side*4-dist+flatPoints[i]; 
                // 从 flatPoints[i] 开始看看能不能找到 k 个符合要求的元素组成序列
                bool found=true;
                long long prevCoord=flatPoints[i]; // 前一个元素的坐标
                for(int c=1;c<k;c++){
                    // prevCoord 已经算一个点,所以还需要找 k-1 个
                    // 距离 prevCoord 至少有 dist 的下一个元素
                    int idx=lb(flatPoints,i+1,prevCoord+dist);
                    if(idx==-1||flatPoints[idx]>upper){
                        // 没有找到,或者触及上界 side*4 - dist
                        found=false;
                        break;
                    }
                    prevCoord=flatPoints[idx];
                }
                if(found){
                    // 有这样的序列
                    return true;
                }
            }
            return false;
        };

        // 二分找最终的结果 dist,找上界
        int distL=1,distR=side;
        int res=0;
        while(distL<=distR){
            int dist=distL+((distR-distL)>>1);
            // 现在限定选中序列中相邻两点之间距离至少为 dist

            if(check(dist)){
                // 如果这个 dist 下可以就缩减左边界
                distL=dist+1;
                res=dist;
            }else{
                // 否则缩减右边界
                distR=dist-1;
            }
        }
        return res;
    }
};

4 个帖子 - 4 位参与者

阅读完整话题

来源: linux.do查看原文