问题描述
A市市长小F计划在市区内选址新建一个火车站,以便市民的日常出行。市区布局呈规则网格状,两个位置[x1, y1]与[x2, y2]之间的距离采用曼哈顿距离计算:|x1 - x2| + |y1 - y2|。
市政府已初步选定了M个备选地址,现需要确定一个位置作为火车站选址,目标是使所有市民到火车站的总曼哈顿距离最短。如果存在多个符合条件的选址,则选择最先出现的地址。
输入数据包括:
N:市民总人数;M:备选火车站位置数;citizens:市民居住位置列表,每个元素为一个坐标[x_i, y_i];locations:备选火车站位置列表,每个元素为一个坐标[p_i, q_i]。
输出:最优火车站位置[p, q]。
示例输入与输出
示例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]
解题思路
曼哈顿距离的分解
曼哈顿距离可以分解为两个维度的绝对差值之和:
d(x1,y1,x2,y2)=∣x1−x2∣+∣y1−y2∣d(x1, y1, x2, y2) = |x1 - x2| + |y1 - y2|
因此,总距离的计算可以分别在x轴和y轴维度独立进行。通过这种分解,问题规模大大简化。
优化策略
-
前缀和优化绝对差值计算
- 将所有市民的
x坐标排序后,利用前缀和快速计算任意位置到所有市民的距离之和。类似地处理y坐标。 - 排序和前缀和的构建时间复杂度为
O(N log N)。
- 将所有市民的
-
二分查找
- 对每个候选位置的
x和y坐标,利用二分查找确定该坐标在市民坐标排序数组中的位置。 - 结合前缀和,快速求解两侧距离。
- 对每个候选位置的
-
逐一评估候选位置
- 计算所有候选位置的总距离,记录最小值。如果存在多个最优解,选择最早出现的候选位置。
实现步骤
-
分离并排序坐标
- 将市民的
x和y坐标分别提取,排序后构建前缀和数组。
- 将市民的
-
计算候选位置距离
- 对每个候选位置,分别计算
x和y方向的总距离,最后求和。
- 对每个候选位置,分别计算
-
记录最优结果
- 通过遍历候选位置,找到最小总距离的候选位置,并输出其坐标。
算法实现
以下是基于 Python 的代码实现:
import bisect
def find_optimal_station(N, M, citizens, candidates):
# 提取并排序市民的x和y坐标
x_coords = [x[0] for x in citizens]
y_coords = [x[1] for x in citizens]
x_sorted = sorted(x_coords)
y_sorted = sorted(y_coords)
# 构建前缀和
prefix_x = [0] * (N + 1)
prefix_y = [0] * (N + 1)
for i in range(N):
prefix_x[i + 1] = prefix_x[i] + x_sorted[i]
prefix_y[i + 1] = prefix_y[i] + y_sorted[i]
# 遍历候选位置
min_distance = float('inf')
best_location = None
for p, q in candidates:
# 计算x方向总距离
pos_x = bisect.bisect_left(x_sorted, p)
dist_x = p * pos_x - prefix_x[pos_x] + (prefix_x[N] - prefix_x[pos_x]) - p * (N - pos_x)
# 计算y方向总距离
pos_y = bisect.bisect_left(y_sorted, q)
dist_y = q * pos_y - prefix_y[pos_y] + (prefix_y[N] - prefix_y[pos_y]) - q * (N - pos_y)
# 总距离
total_distance = dist_x + dist_y
# 更新最优解
if total_distance < min_distance:
min_distance = total_distance
best_location = (p, q)
return list(best_location)
# 测试样例
if __name__ == "__main__":
citizens1 = [[-1, -1], [-1, 1], [1, -1], [1, 1]]
locations1 = [[3, 2], [1, 0], [0, 0]]
print(find_optimal_station(4, 3, citizens1, locations1)) # 输出:[1, 0]
时间复杂度分析
- 排序和前缀和构建:
- 排序市民坐标数组的时间复杂度为
O(N log N)。 - 构建前缀和的时间复杂度为
O(N)。
- 排序市民坐标数组的时间复杂度为
- 候选位置评估:
- 对每个候选位置,在
x和y两个维度上分别进行二分查找,时间复杂度为O(log N)。 - 因此,总时间复杂度为
O(M log N)。
- 对每个候选位置,在
总时间复杂度为:
O(NlogN+MlogN)O(N \log N + M \log N)
空间复杂度分析
- 主要使用了排序后的坐标数组和前缀和数组,总空间复杂度为
O(N)。
测试样例设计
- 常规情况:市民和候选位置分布在网格内,验证算法的正确性。
- 边界情况:
N = 1或M = 1:只有一个市民或一个候选位置。- 市民和候选位置重叠:验证算法能否正确返回。
- 极端情况:
- 所有市民的坐标相同。
- 市民数量或候选位置数量达到最大值(如
100,000)。
总结与反思
-
算法优势:
- 分解曼哈顿距离简化了问题复杂度。
- 使用排序、前缀和和二分查找高效处理大规模数据。
-
改进方向:
- 如果候选位置过多,可以结合分块优化进一步减少计算开销。
- 在实际场景中,可能需要考虑更复杂的约束条件(如地形、成本等)。
-
拓展思考:
- 曼哈顿距离的优化策略可应用于类似问题,如配送中心选址、网格路径规划等场景。
- 可尝试拓展到三维坐标或其他距离度量方式(如欧几里得距离)。