以下为题目描述
问题描述
小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));
}
}
代码解释
- 提取市民的坐标并排序
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);
• 将所有市民的 x 和 y 坐标分别存储在 xCoords 和 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];
}
• 前缀和数组 prefixSumX 和 prefixSumY 用于快速计算某个位置之前或之后所有坐标的和
• prefixSumX[i] 表示排序后的第 0 到第 i-1 个市民的 x 坐标之和
- 遍历每个候选火车站位置,计算总距离
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 的元素的索引