Leetcode双周赛28 安排邮筒问题
问题内容:给你一个房屋数组houses 和一个整数 k ,其中 houses[i] 是第 i 栋房子在一条街上的位置,现需要在这条街上安排 k 个邮筒。 1 < = h o u s e s . l e n g t h < = 100 , 1 < = h o u s e s [ i ] < = 1 0 4 , 1 < = k < = n 1<=houses.length<=100,1<=houses[i]<=10^4,1<=k<=n 1<=houses.length<=100,1<=houses[i]<=104,1<=k<=n,且houses中数字各不相同。
请你返回每栋房子与离它最近的邮筒之间的距离的 最小 总和。
答案保证在 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 。
总结下来就是直线上n个点,安排k个点,使他们到最近点的距离之和最短。开始在想是不是要搜索或者二分,因为第三题已经是dp了。。没想到,然后发现是个dp问题,就是在n个点里插k个点的模型。
容易想到dp[i][j]表示0-i范围内插j个邮筒。然后穷举分割点k,即最后一段k+1~j公用一个邮筒,之前的问题就变成了dp[k][j-1]。这样做的复杂度是 O ( n 3 ) O(n^3) O(n3)的,看看数据量,100,OK正好。
现在关键是要求k+1~j范围内插一个邮筒怎么插?转化一下也就是,直线上有n个点,安排一个位置使得到这n个点的距离之和最短。这个模型就简单了,似乎是个小学的题。结论是,当有奇数个点的时候,选择中间的点;当有偶数个点,选中间俩都可以。如果硬要说为什么,可以简单证明一下。n是奇数时,假设现在已经选在中间,向左移动 Δ x \Delta x Δx,有 n + 1 2 \frac{n+1}{2} 2n+1个点距离增加了 Δ x \Delta x Δx, n − 1 2 \frac{n-1}{2} 2n−1个点距离减少了 Δ x \Delta x Δx。向右移动同理。或者更直观的看法,看成一对一对点的距离之和,中间那个点的距离为0时总的和最小。 n是偶数时,运用这种方法显然可证在中位数两个点之间距离之和不变。
所以产生的附加问题我们解决了,现在就可以解决大问题了。思路就是,先预处理得到dist[i][j]表示i~j范围内建一个邮筒造成的总距离,然后运用上面的递推方程,更新dp[i][k]。代码如下。
class Solution {
public:
int minDistance(vector<int>& houses, int k) {
int n=houses.size();
int dist[n][n];
int dp[n][k+1];
memset(dist,0,sizeof(dist));
sort(houses.begin(),houses.end());//别忘了排序
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
dist[i][j]=dist[i][j-1]+abs(houses[j]-houses[(i+j)/2]);
}
}//i到j放一个邮筒
for(int i=0;i<n;i++){
dp[i][1]=dist[0][i];
if(i+1<=k) dp[i][i+1]=0;
}//初始化
for(int j=2;j<=k;j++){
for(int i=j;i<n;i++){
dp[i][j]=INT_MAX;
for(int kk=j-2;kk<i;kk++){
dp[i][j]=min(dp[i][j],dp[kk][j-1]+dist[kk+1][i]);
}
}
}//递推方程,注意边界点,当时kk的边界点写错了一个wa了好久
return dp[n-1][k];
}
};