3143.正方形中的最多点数

目标

给你一个二维数组 points 和一个字符串 s ,其中 points[i] 表示第 i 个点的坐标,s[i] 表示第 i 个点的 标签 。

如果一个正方形的中心在 (0, 0) ,所有边都平行于坐标轴,且正方形内 不 存在标签相同的两个点,那么我们称这个正方形是 合法 的。

请你返回 合法 正方形中可以包含的 最多 点数。

注意:

  • 如果一个点位于正方形的边上或者在边以内,则认为该点位于正方形内。
  • 正方形的边长可以为零。

示例 1:

输入:points = [[2,2],[-1,-2],[-4,4],[-3,1],[3,-3]], s = "abdca"
输出:2
解释:边长为 4 的正方形包含两个点 points[0] 和 points[1] 。

示例 2:

输入:points = [[1,1],[-2,-2],[-2,2]], s = "abb"
输出:1
解释:边长为 2 的正方形包含 1 个点 points[0] 。

示例 3:

输入:points = [[1,1],[-1,-1],[2,-2]], s = "ccd"
输出:0
解释:任何正方形都无法只包含 points[0] 和 points[1] 中的一个点,所以合法正方形中都不包含任何点。

说明:

  • 1 <= s.length, points.length <= 10^5
  • points[i].length == 2
  • -10^9 <= points[i][0], points[i][1] <= 10^9
  • s.length == points.length
  • points 中的点坐标互不相同。
  • s 只包含小写英文字母。

思路

坐标平面上有一些点points,每个点都有一个标签字母,points[i] 对应的标签为给定字符串s相应位置上的字母 s.charAt(i)。问以坐标轴原点为中心的正方形最多能覆盖多少标签不同的坐标点。

坐标平面内的点如果想要被以原点为中心的正方形覆盖,那么正方形的边长应大于等于max(|x|,|y|),即切比雪夫距离。又要使坐标点的标签不同,可以将坐标点的 切比雪夫距离 从小到大排序,然后依次遍历并将其标签加入哈希表,直到遇到重复的为止。需要注意,应将该边长上的所有已经加入的坐标点删除,为此我们可以将距离相同的坐标放入一个集合,要么都计入,要么结束循环。

看了题解,网友还提供了两种解法,一个是使用二分法枚举边长范围,看正方形内是否有重复坐标;另一个是建立 字母 <---> 最小距离 的映射,同时计算最小的 次小距离,统计映射中的最小距离 小于 最小 次小距离 的坐标点个数即可。

代码


/**
 * @date 2024-08-03 21:44
 */
public class MaxPointsInsideSquare3143 {

    public int maxPointsInsideSquare(int[][] points, String s) {
        int n = points.length;
        Map<Integer, List<Integer>> map = new TreeMap<>();
        for (int i = 0; i < n; i++) {
            int key = Math.max(Math.abs(points[i][0]), Math.abs(points[i][1]));
            map.putIfAbsent(key, new ArrayList<>());
            map.get(key).add(i);
        }
        Set<Character> labelSet = new HashSet<>();
        int res = 0;
        here:
        for (Map.Entry<Integer, List<Integer>> entry : map.entrySet()) {
            for (Integer i : entry.getValue()) {
                if (labelSet.contains(s.charAt(i))) {
                    break here;
                }
                labelSet.add(s.charAt(i));
            }
            res += entry.getValue().size();
        }
        return res;
    }

}

性能

3102.最小化曼哈顿距离

目标

给你一个下标从 0 开始的数组 points ,它表示二维平面上一些点的整数坐标,其中 points[i] = [xi, yi] 。

两点之间的距离定义为它们的 曼哈顿距离。两个单元格 (xi, yi) 和 (xj, yj) 之间的曼哈顿距离为 |xi - xj| + |yi - yj|。

请你恰好移除一个点,返回移除后任意两点之间的 最大 距离可能的 最小 值。

示例 1:

输入:points = [[3,10],[5,15],[10,2],[4,4]]
输出:12
解释:移除每个点后的最大距离如下所示:
- 移除第 0 个点后,最大距离在点 (5, 15) 和 (10, 2) 之间,为 |5 - 10| + |15 - 2| = 18 。
- 移除第 1 个点后,最大距离在点 (3, 10) 和 (10, 2) 之间,为 |3 - 10| + |10 - 2| = 15 。
- 移除第 2 个点后,最大距离在点 (5, 15) 和 (4, 4) 之间,为 |5 - 4| + |15 - 4| = 12 。
- 移除第 3 个点后,最大距离在点 (5, 15) 和 (10, 2) 之间的,为 |5 - 10| + |15 - 2| = 18 。
在恰好移除一个点后,任意两点之间的最大距离可能的最小值是 12 。

示例 2:

输入:points = [[1,1],[1,1],[1,1]]
输出:0
解释:移除任一点后,任意两点之间的最大距离都是 0 。

说明:

  • 3 <= points.length <= 10^5
  • points[i].length == 2
  • 1 <= points[i][0], points[i][1] <= 10^8

提示:

  • Notice that the Manhattan distance between two points [xi, yi] and [xj, yj] is max({xi - xj + yi - yj, xi - xj - yi + yj, - xi + xj + yi - yj, - xi + xj - yi + yj}).
  • If you replace points as [xi - yi, xi + yi] then the Manhattan distance is max(max(xi) - min(xi), max(yi) - min(yi)) over all i.
  • After those observations, the problem just becomes a simulation. Create multiset of points [xi - yi, xi + yi], you can iterate on a point you might remove and get the maximum Manhattan distance over all other points.

思路

有一些二维平面上的整数坐标点,移除其中一个点,求任意两点之间曼哈顿距离最大值的最小值。

n个点之间的曼哈顿距离有C(n,2) = n * (n - 1) / 2 个,移除其中一个点,最大值可能发生变化,需要找到其中最小的。

问题的关键是如何计算n个点之间曼哈顿距离的最大值,如果暴力计算,找出最大值的时间复杂度是 O(n^2),还要依次移除一个点找到其中的最小值,综合起来时间复杂度是 O(n^3)。点的个数最多 10^5,暴力求解会超时。

我么需要考虑曼哈顿距离有什么特性,找出最大值需要每一个都遍历吗?移除一个点对最大值有什么影响,需要重新计算吗?

看了题目的提示是使用变量替换将问题转化:两点(xi, yi) 与 (xj, yj) 之间的曼哈顿距离为|xi - xj| + |yi - yj|,去掉绝对值即 max({xi - xj + yi - yj, xi - xj - yi + yj, - xi + xj + yi - yj, - xi + xj - yi + yj}),令 ui = xi - yi, vi = xi + yi,则点 (xi, yi) 与 (xj, yj) 之间的曼哈顿距离转换为 max({vi - vj, ui - uj, uj - ui, vj - vi}) = max({|vi - vj|, |ui- uj|})对于所有的 i,j,曼哈顿距离的最大值为 max({max(vi) - min(vi), max(ui) - min(ui)})。这样找出曼哈顿距离最大值的时间复杂度减少为 O(n)

于是,我们只需要将坐标点转化为 (ui, vi),依次去除某个坐标点,再找出 u、v 的最大值与最小值即可。试了一下,O(n^2)的时间复杂度仍会超时。

我们可以记录一下vi、ui取最大最小值时的坐标,然后排除这几个坐标点后找出其中的最小值,最坏的情况即坐标没有重复,时间复杂度为O(4n)。

我也尝试了记录次大与次小值,试图将复杂度降为 O(n),但需要考虑去除掉相应坐标点后,另一个坐标是否会受到影响。例如,max({max(vi) - min(vi), max(ui) - min(ui)}) 假如我们删去了vi最大的坐标,取次大的vi,但是后面ui的最大与最小是否受到影响?

因此我们不仅需要一次遍历获取到最大与次大,还要再次遍历比较当前值是否是最大或最小,如果是则取次大或次小。需要注意,这里的最大与次大以及最小与次小的值可以相同。更进一步,如果我们同时保存了x、y 取最大、最小的四个坐标,我们只需排除这4个坐标即可,其它坐标对最大距离没有影响。

看了官网题解,转换后的距离称为切比雪夫距离。事实上,max({|vi - vj|, |ui - uj|}) 计算的是 (vi, ui) (vj, uj) 两点曼哈顿距离投影到 x 轴(|vi - vj|) 和 y 轴(|ui - uj|)的线段长度的最大值,即切比雪夫距离。注意本题中的u、v对应题解中v、u,并且使用的是x-y,而非y-x,不过对于求解没有什么影响,无非是关于坐标轴镜像了一下。

代码

/**
 * @date 2024-07-09 10:20
 */
public class MinimumDistance3102 {

    public int minimumDistance(int[][] points) {
        for (int[] point : points) {
            int u = point[0] - point[1];
            int v = point[0] + point[1];
            point[0] = u;
            point[1] = v;
        }
        int n = points.length;
        int res = Integer.MAX_VALUE;
        int maxX = Integer.MIN_VALUE;
        int minX = Integer.MAX_VALUE;
        int maxY = Integer.MIN_VALUE;
        int minY = Integer.MAX_VALUE;
        int maxXIndex = -1, minXIndex = -1, maxYIndex = -1, minYIndex = -1;
        List<Integer> exclude = new ArrayList<>();
        for (int j = 0; j < n; j++) {
            if (points[j][0] > maxX) {
                maxX = points[j][0];
                maxXIndex = j;
            }
            if (points[j][0] < minX) {
                minX = points[j][0];
                minXIndex = j;
            }
            if (points[j][1] > maxY) {
                maxY = points[j][1];
                maxYIndex = j;
            }
            if (points[j][1] < minY) {
                minY = points[j][1];
                minYIndex = j;
            }
        }
        exclude.add(maxXIndex);
        exclude.add(minXIndex);
        exclude.add(maxYIndex);
        exclude.add(minYIndex);
        for (Integer i : exclude) {
            maxX = Integer.MIN_VALUE;
            minX = Integer.MAX_VALUE;
            maxY = Integer.MIN_VALUE;
            minY = Integer.MAX_VALUE;
            for (int j = 0; j < n; j++) {
                if (i != j) {
                    maxX = Math.max(maxX, points[j][0]);
                    minX = Math.min(minX, points[j][0]);
                    maxY = Math.max(maxY, points[j][1]);
                    minY = Math.min(minY, points[j][1]);
                }
            }
            res = Math.min(res, Math.max(maxX - minX, maxY - minY));
        }
        return res;
    }
}

性能