刷题练习——82. 理想火车站定位

77 阅读4分钟

以下为题目描述

问题描述

小F是A市的市长,正在计划在A市新建一个火车站以方便市民的日常出行。市区内的街道布局十分规整,形成网格状。从一个位置[x1, y1]到另一个位置[x2, y2]的距离计算方法为 |x1 - x2| + |y1 - y2|,即曼哈顿距离。

在初步考察后,市政府列出了M个可能的火车站建设点。为了使得市民到火车站的总旅行时间最短,小F希望选出一个最优位置作为火车站的地址。

请你帮助小F计算出哪一个位置最适合建设新火车站。

  • N: 市民的总人数。
  • M: 可建设火车站的备选位置数。
  • citizens: 一个列表,每个元素是一个元组 [x_i, y_i],表示第 i 位市民的居住位置。
  • locations: 一个列表,每个元素是一个元组 [p_i, q_i],表示第 i 个备选的火车站位置。

如果有多个火车站最优,那么选择第一次出现的那个。


测试样例

样例1:

输入:n = 4,m = 3,citizens = [[-1, -1], [-1, 1], [1, -1], [1, 1]],locations = [[3, 2], [1, 0], [0, 0]]
输出:[1, 0]

样例2:

输入:n = 2,m = 2,citizens = [[0, 0], [0, 4]],locations = [[0, 2], [0, 3]]
输出:[0, 2]

样例3:

输入:n = 3,m = 1,citizens = [[10, 10], [20, 20], [30, 30]],locations = [[15, 15]]
输出:[15, 15]

样例4:

输入:n = 5,m = 3,citizens = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]],locations = [[4, 5], [6, 7], [8, 9]]
输出:[4, 5]

样例5:

输入:n = 6,m = 2,citizens = [[10, 10], [20, 20], [30, 30], [40, 40], [50, 50], [60, 60]],locations = [[35, 35], [45, 45]]
输出:[35, 35]

解决方案

public class Main {
    public static int[] solution(int n, int m, int[][] citizens, int[][] locations) {
        // 提取市民的 x 和 y 坐标
        int[] xCoords = new int[n];
        int[] yCoords = new int[n];
        for (int i = 0; i < n; i++) {
            xCoords[i] = citizens[i][0];
            yCoords[i] = citizens[i][1];
        }

        // 对坐标进行排序
        java.util.Arrays.sort(xCoords);
        java.util.Arrays.sort(yCoords);

        // 计算前缀和
        long[] prefixSumX = new long[n + 1];
        long[] prefixSumY = new long[n + 1];
        for (int i = 0; i < n; i++) {
            prefixSumX[i + 1] = prefixSumX[i] + xCoords[i];
            prefixSumY[i + 1] = prefixSumY[i] + yCoords[i];
        }

        // 初始化最小总距离和最佳位置索引
        long minTotalDistance = Long.MAX_VALUE;
        int bestIndex = -1;

        // 遍历每个候选火车站位置
        for (int locIndex = 0; locIndex < m; locIndex++) {
            int p = locations[locIndex][0];
            int q = locations[locIndex][1];

            // 计算在 x 方向上的总距离
            long totalDistX = computeTotalDistance(xCoords, prefixSumX, p);
            // 计算在 y 方向上的总距离
            long totalDistY = computeTotalDistance(yCoords, prefixSumY, q);

            long totalDistance = totalDistX + totalDistY;

            // 更新最小总距离和最佳位置
            if (totalDistance < minTotalDistance) {
                minTotalDistance = totalDistance;
                bestIndex = locIndex;
            }
        }

        // 返回最佳位置
        return new int[]{locations[bestIndex][0], locations[bestIndex][1]};
    }

    // 计算一个维度上的总距离
    public static long computeTotalDistance(int[] coords, long[] prefixSum, int pos) {
        int n = coords.length;
        int index = lowerBound(coords, pos);
        long leftSum = (long) pos * index - prefixSum[index];
        long rightSum = (prefixSum[n] - prefixSum[index]) - (long) pos * (n - index);
        return leftSum + rightSum;
    }

    // 实现二分查找的 lowerBound 方法
    public static int lowerBound(int[] arr, int target) {
        int left = 0;
        int right = arr.length; // 右边界为数组长度
        while (left < right) {
            int mid = (left + right) / 2;
            if (arr[mid] < target) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }

    public static boolean arrayEqual(int[] a, int[] b) {
        if (a.length != b.length) {
            return false;
        }
        for (int i = 0; i < a.length; i++) {
            if (a[i] != b[i]) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        // You can add more test cases here
        int[][] citizens1 = {{-1, -1}, {-1, 1}, {1, -1}, {1, 1}};
        int[][] locations1 = {{3, 2}, {1, 0}, {0, 0}};

        int[] result = solution(4, 3, citizens1, locations1);
        int[] expected = {1, 0};
        System.out.println(arrayEqual(result, expected));
    }
}

代码解释

  1. 提取市民的坐标并排序
int[] xCoords = new int[n];
int[] yCoords = new int[n];
for (int i = 0; i < n; i++) {
    xCoords[i] = citizens[i][0];
    yCoords[i] = citizens[i][1];
}

Arrays.sort(xCoords);
Arrays.sort(yCoords);

• 将所有市民的 xy 坐标分别存储在 xCoords 和 yCoords 数组中

• 对这两个数组进行排序,方便后续计算

  1. 计算前缀和数组
long[] prefixSumX = new long[n + 1];
long[] prefixSumY = new long[n + 1];
for (int i = 0; i < n; i++) {
    prefixSumX[i + 1] = prefixSumX[i] + xCoords[i];
    prefixSumY[i + 1] = prefixSumY[i] + yCoords[i];
}

• 前缀和数组 prefixSumX 和 prefixSumY 用于快速计算某个位置之前或之后所有坐标的和

• prefixSumX[i] 表示排序后的第 0 到第 i-1 个市民的 x 坐标之和

  1. 遍历每个候选火车站位置,计算总距离
for (int locIndex = 0; locIndex < m; locIndex++) {
    int p = locations[locIndex][0];
    int q = locations[locIndex][1];

    // 计算在 x 和 y 方向上的总距离
    long totalDistX = computeTotalDistance(xCoords, prefixSumX, p);
    long totalDistY = computeTotalDistance(yCoords, prefixSumY, q);

    long totalDistance = totalDistX + totalDistY;

    // 更新最小总距离和最佳位置
    if (totalDistance < minTotalDistance) {
        minTotalDistance = totalDistance;
        bestIndex = locIndex;
    }
}

• 对于每个候选位置 [p, q]:

分别计算市民在 **x****y** 方向到候选位置的总距离
总曼哈顿距离为两个方向距离之和
如果总距离小于当前最小值,更新 minTotalDistance 和 bestIndex

4. 实现二分查找的 lowerBound 方法

public static int lowerBound(int[] arr, int target) {
    int left = 0;
    int right = arr.length;
    while (left < right) {
        int mid = left + (right - left) / 2;
        if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}

• 在已排序的数组 arr 中,找到第一个不小于 target 的元素的索引