Hard
题目描述
给你一个房屋数组 houses 和一个整数 k ,其中 houses[i] 是第 i 栋房子在一条街上的位置,现需要在这条街上安排 k 个邮箱。
请你返回每栋房子与离它最近的邮箱之间的距离的 最小 总和。
答案保证在 32 位有符号整数范围以内。
示例 1:
输入:houses = [1,4,8,10,20], k = 3
输出:5
解释:将邮箱分别安装在位置 3, 9 和 20 处。
每个房子到最近邮箱的距离和为 |3-1| + |4-3| + |9-8| + |10-9| + |20-20| = 5 。
示例 2:
输入:houses = [2,3,5,12,18], k = 2
输出:9
解释:将邮箱分别安装在位置 3 和 14 处。
每个房子到最近邮箱的距离和为 |2-3| + |3-3| + |5-3| + |12-14| + |18-14| = 9 。
提示:
1 <= k <= houses.length <= 1001 <= houses[i] <= 10^4houses中的整数互不相同
思路提示:
- 如果 k=1,最小距离通过将邮箱分配在房屋数组的中位数处获得
- 推广这个思路,使用动态规划分配 k 个邮箱
解题思路
这是一个典型的动态规划问题,核心思路如下:
关键观察:
- 对于任意连续区间的房屋,最优的邮箱位置是中位数位置,这样能使总距离最小
- 我们需要将房屋分成k个连续的区间,每个区间放置一个邮箱
解法思路:
- 首先对房屋位置进行排序,确保我们处理的是有序序列
- 预处理:计算任意区间[i,j]内放置一个邮箱的最小代价cost[i][j]
- 动态规划:定义dp[i][j]表示前i栋房子使用j个邮箱的最小总距离
- 状态转移:dp[i][j] = min(dp[t][j-1] + cost[t+1][i]) 对所有有效的t
预处理cost数组:
- 对于区间[i,j],最优邮箱位置是中位数houses[(i+j)/2]
- cost[i][j]就是该区间所有房屋到中位数的距离之和
时间复杂度: 预处理O(n³),DP O(n²k) 空间复杂度: O(n²)用于cost数组,O(nk)用于DP数组
代码实现
class Solution {
public:
int minDistance(vector<int>& houses, int k) {
int n = houses.size();
sort(houses.begin(), houses.end());
// 预处理:计算区间[i,j]放置一个邮箱的最小代价
vector<vector<int>> cost(n, vector<int>(n, 0));
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int median = houses[(i + j) / 2];
for (int t = i; t <= j; t++) {
cost[i][j] += abs(houses[t] - median);
}
}
}
// dp[i][j]表示前i个房子用j个邮箱的最小代价
vector<vector<int>> dp(n + 1, vector<int>(k + 1, INT_MAX));
dp[0][0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= min(i, k); j++) {
for (int t = j - 1; t < i; t++) {
if (dp[t][j-1] != INT_MAX) {
dp[i][j] = min(dp[i][j], dp[t][j-1] + cost[t][i-1]);
}
}
}
}
return dp[n][k];
}
};
class Solution:
def minDistance(self, houses: List[int], k: int) -> int:
n = len(houses)
houses.sort()
# 预处理:计算区间[i,j]放置一个邮箱的最小代价
cost = [[0] * n for _ in range(n)]
for i in range(n):
for j in range(i, n):
median = houses[(i + j) // 2]
for t in range(i, j + 1):
cost[i][j] += abs(houses[t] - median)
# dp[i][j]表示前i个房子用j个邮箱的最小代价
dp = [[float('inf')] * (k + 1) for _ in range(n + 1)]
dp[0][0] = 0
for i in range(1, n + 1):
for j in range(1, min(i, k) + 1):
for t in range(j - 1, i):
if dp[t][j-1] != float('inf'):
dp[i][j] = min(dp[i][j], dp[t][j-1] + cost[t][i-1])
return dp[n][k]
public class Solution {
public int MinDistance(int[] houses, int k) {
int n = houses.Length;
Array.Sort(houses);
// 预处理:计算区间[i,j]放置一个邮箱的最小代价
int[,] cost = new int[n, n];
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
int median = houses[(i + j) / 2];
for (int t = i; t <= j; t++) {
cost[i, j] += Math.Abs(houses[t] - median);
}
}
}
// dp[i,j]表示前i个房子用j个邮箱的最小代价
int[,] dp = new int[n + 1, k + 1];
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= k; j++) {
dp[i, j] = int.MaxValue;
}
}
dp[0, 0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= Math.Min(i, k); j++) {
for (int t = j - 1; t < i; t++) {
if (dp[t, j-1] != int.MaxValue) {
dp[i, j] = Math.Min(dp[i, j], dp[t, j-1] + cost[t, i-1]);
}
}
}
}
return dp[n, k];
}
}
var minDistance = function(houses, k) {
const n = houses.length;
houses.sort((a, b) => a - b);
// 预处理:计算区间[i,j]放置一个邮箱的最小代价
const cost = Array(n).fill().map(() => Array(n).fill(0));
for (let i = 0; i < n; i++) {
for (let j = i; j < n; j++) {
const median = houses[Math.floor((i + j) / 2)];
for (let t = i; t <= j; t++) {
cost[i][j] += Math.abs(houses[t] - median);
}
}
}
// dp[i][j]表示前i个房子用j个邮箱的最小代价
const dp = Array(n + 1).fill().map(() => Array(k + 1).fill(Infinity));
dp[0][0] = 0;
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= Math.min(i, k); j++) {
for (let t = j - 1; t < i; t++) {
if (dp[t][j-1] !== Infinity) {
dp[i][j] = Math.min(dp[i][j], dp[t][j-1] + cost[t][i-1]);
}
}
}
}
return dp[n][k];
};
复杂度分析
| 算法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 动态规划 | O(n³ + n²k) | O(n² + nk) |
| 说明 | 预处理cost数组需要O(n³),DP过程需要O(n²k) | cost数组占用O(n²),dp数组占用O(nk) |