1478-安排邮筒
给你一个房屋数组houses
和一个整数 k
,其中 houses[i]
是第 i
栋房子在一条街上的位置,现需要在这条街上安排 k
个邮筒。
请你返回每栋房子与离它最近的邮筒之间的距离的 最小 总和。
答案保证在 32 位有符号整数范围以内。
示例 1:
![](https://assets.leetcode-cn.com/aliyun-lc-
upload/uploads/2020/06/13/sample_11_1816.png)
**输入:** houses = [1,4,8,10,20], k = 3
**输出:** 5
**解释:** 将邮筒分别安放在位置 3, 9 和 20 处。
每个房子到最近邮筒的距离和为 |3-1| + |4-3| + |9-8| + |10-9| + |20-20| = 5 。
示例 2:
![](https://assets.leetcode-cn.com/aliyun-lc-
upload/uploads/2020/06/13/sample_2_1816.png)
**输入:** houses = [2,3,5,12,18], k = 2
**输出:** 9
**解释:** 将邮筒分别安放在位置 3 和 14 处。
每个房子到最近邮筒距离和为 |2-3| + |3-3| + |5-3| + |12-14| + |18-14| = 9 。
示例 3:
**输入:** houses = [7,4,6,1], k = 1
**输出:** 8
示例 4:
**输入:** houses = [3,6,14,10], k = 4
**输出:** 0
提示:
n == houses.length
1 <= n <= 100
1 <= houses[i] <= 10^4
1 <= k <= n
- 数组
houses
中的整数互不相同。
方法一:动态规划
思路与算法
我们称「一个邮筒负责一栋房子」,当且仅当该邮筒是距离该房子最近的邮筒。那么对于任意一个邮筒,它负责的房子的集合在数轴上是连续的。因此,我们可以将数组 houses 进行排序,这样就可以方便地给这些房子安排邮筒了。
我们用 f[i][j] 表示给前 i 栋房子(从 0 开始编号)安排 j 个邮筒(从 1 开始编号)的最小距离总和。在进行状态转移时,我们可以枚举第 j 个邮筒负责的房子的起始编号,从 i_0+1 开始,到 i 结束。这样我们就可以写出状态转移方程:
f[i][j] = \min(f[i_0][j-1] + \mathrm{cost}(i_0+1, i))
以及边界条件
f[i][1] = \mathrm{cost}(0, i)
其中 \mathrm{cost}(l, r) 表示给排好序的 houses}[l] 到 houses}[r] 的房子安排一个邮筒,可以得到的最小距离总和。那么如何计算 \mathrm{cost}(l, r) 呢?设想一下,如果在数轴上有若干个点组成了集合 \mathcal{X,我们需要找出一个目标点 x,使得 \mathcal{X 到 x 的距离和最小。那么我们应当选择 \mathcal{X 的中位数作为目标点,即 x = \mathrm{median}(\mathcal{X})。
这个结论是比较直观的:选择中位数作为目标点,使得 \mathcal{X 中小于 x 的点数恰好等于 \mathcal{X 中大于 x 的点数,达到了一个平衡。如果我们选择的目标点小于中位数,那么 \mathcal{X 中大于 x 的点数要多一些,我们将目标点向中位数的方向(向右)移动,距离和就会减小。同理可得如果我们选择的目标点大于中位数,那么将目标点向左移动,距离和就会减小。因此选择中位数作为目标点是最优的。
如果读者希望得到具体的证明,可以参考下面「正确性证明」的部分。
当 x = \mathrm{median}(\mathcal{X}) 时,\mathrm{cost}(l, r) 的计算也并不复杂:由于 houses}[l .. r] 的中位数与 houses}[l+1 .. r-1] 的中位数是相同的,而 houses}[l] 和 houses}[r] 与中位数的距离和恰好等于 houses}[r] - \textit{houses}[l]。因此,我们可以使用递推的方法计算 \mathrm{cost}(l, r),即:
\mathrm{cost}(l, r) = \mathrm{cost}(l+1, r-1) + (\textit{houses}[r] - \textit{houses}[l])
我们可以使用 O(n^2) 的时间预处理出所有的 \mathrm{cost}(l, r),其中 n 是数组 houses 的长度。这样一来,我们就在动态规划的部分快速地进行状态转移了。
最终的答案即为 f[n-1][k]。
细节
在动态规划中,有一些状态是无意义的,我们可以不必去计算。对于 f[i][j],显然需要满足 j \leq i+1,也就是邮筒的数量不会超过房子的数量。对于这些状态,我们可以在对数组 f 进行初始化的时候,将它们赋值成一个很大的数(因为状态转移方程中为 \min),并且在动态规划的过程中不去枚举这些状态,这样这些无意义的状态既不会被计算,也不会参与状态转移。
正确性证明
设数组 a 包含 n 个元素 a[1], a[2], \cdots, a[n],且它们互不相同。下证当 x = \mathrm{median}(a) 时,
\sum_{i=1}^n |a[i] - x|
取到最小值,且最小值为
\sum_{i=1}^{\lfloor n/2 \rfloor} (a[n+1-i] - a[i])
证明:将数组 a 进行升序排序,并补充 a[0] = -\infty 以及 a[n+1] = \infty。记
f(x) = \sum_{i=1}^n |a[i] - x|
求导可得
f’(x) = \sum_{i=1}^n \mathbb{I}(a[i])
其中 \mathbb{I}(a) 为示性函数
\mathbb{I}(a) = \begin{cases}
1, & x > a[i] \
0, & x = a[i] \
-1, & x < a[i]
\end{cases}
由于 a[0] 到 a[n+1] 有序,那么存在 i_0 \in [0, n+1) 使得 a[i_0] \leq x < a[i_0+1]。
如果 a[i_0] < x < a[i_0+1],那么
f’(x) = \sum_{i=1}^{i_0} 1 + \sum_{i=i_0+1}^n (-1) = 2i_0 - n
如果 x = a[i_0],那么 f(x) 在该点连续但不可导。
要想找到 f(x) 的最小值,我们只需要关注 f’(x) 的正负性。由
f’(x) = 2i_0 - n < 0
可得 i_0 \leq \lfloor (n-1)/2 \rfloor,即当 x < a[\lfloor (n+1)/2 \rfloor] 时,f’(x) < 0,f(x) 单调减。同理可得,当 i_0 \geq \lceil (n+1)/2 \rceil,即当 x > a[\lceil (n+1)/2 \rceil] 时,f’(x) > 0,f(x) 单调增。也就是说:
当 n 为奇数时,\lfloor (n+1)/2 \rfloor = \lceil (n+1)/2 \rceil = (n+1)/2,f(x) 在 x = a[(n+1)/2] 处取到最小值,对应了数组 a 的中位数;
当 n 为偶数时,\lfloor (n+1)/2 \rfloor = n/2,\lceil (n+1)/2 \rceil = n/2+1,f(x) 在 x \in [a[n/2], a[n/2+1]] 处取到最小值,而数组 a 的中位数 1/2 \cdot (a[n/2] + a[n/2+1]) 就包含在该区间中。
因此我们可以取 x = \mathrm{median}(a)。此时,对应的最小值为:
\begin{aligned}
\sum_{i=1}^n |a[i] - x| &= \sum_{a_i < x} (x-a[i]) + \sum_{a_i > x} (a[i]-x) \
&= \sum_{i=1}^{\lfloor n/2 \rfloor} (x-a[i]) + \sum_{i=\lceil n/2 \rceil + 1}^{n} (a[i]-x) \
&= \sum_{i=1}^{\lfloor n/2 \rfloor} (x-a[i]) + \sum_{j=1}^{\lfloor n/2 \rfloor} (a[n+1-j]-x) \
&= \sum_{i=1}^{\lfloor n/2 \rfloor} (a[n+1-i] - a[i])
\end{aligned}
其中使用了代换 j=n+1-i 以及一个简单的结论 \lfloor n/2 \rfloor + \lceil n/2 \rceil = n。
1 | class Solution { |
1 | class Solution { |
1 | class Solution: |
复杂度分析
时间复杂度:O(n^2k),其中 n 是数组 houses 的长度。在预处理部分,需要的时间为 O(n^2);在动态规划部分,我们需要 O(nk) 的时间枚举每个状态 f[i][j],并使用 O(n) 的时间枚举 i_0 进行状态转移,相乘即可得到总时间复杂度。
空间复杂度:O(n^2 + nk),即为存储 \mathrm{cost 以及动态规划的状态需要的空间。