Leetcode: 1478. 安排邮桶

题目

给你一个房屋数组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 。


示例 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 中的整数互不相同。

分析

首先,考虑这个问题:给定一个数组,找到一个点,使得该点到数组中所有数的距离和最小。

比如升序序列 [a, b, c],求 \min |x - a| + |x - b| + |x - c|。首先 x \in (a, c), 由绝对值不等式,|x - a| + |x - b| + |x - c| >= |x - a + c - x| + |x - b| = |c - a| + |x - b| >= |c - a|, 即当 x = b 时,上面的式子有最小值 |c - a|。

此外当有偶数的元素时,可以取最中间的两个元素中的任何一个。

由此我们得到,如果只安排一个邮箱的话,将其安排到序列的中位数即可。


其次,定义:dp[i][j] 表示前 i 个房子,用了 j 个邮箱,距离和的最小值。

状态转移:dp[i][j] = min \{dp[x][j - 1] + rec[x + 1][i] | j - 1 <= x <= i - 1\}, 其中 rec[i][j] 表示第 i 个房子到第 j 个房子中加入一个邮箱得到距离和的最小值。

解释:考虑 j - 1 个邮箱负责的房子的个数。由于第 j 个邮箱必须要使用,所以 j - 1 个邮箱最多负责前 i - 1 个房子,第 i 个房子由第 j 个邮箱负责; j - 1 个邮箱最少负责 j - 1 个房子,第 j 到 i 个房子由第 j 个邮箱负责。

注:上面所说的第 i 个房子是 houses[i - 1],以此类推。


代码

class Solution {
    public int minDistance(int[] houses, int k) {
        Arrays.sort(houses);
        int n = houses.length;

        // rec[i][j] 表示第 i 个房子(houses[i - 1]) 到第 j 个房子(houses[j - 1]) 中间放一个邮筒,距离和的最小值
        int[][] rec = new int[n + 1][n + 1];

        for (int i = 1; i <= n; i++) {
            for (int j = i; j <= n; j++) {
                int mid = i + j >> 1;
                for (int x = i; x <= j; x++) {
                    rec[i][j] += Math.abs(houses[mid - 1] - houses[x - 1]);
                } 
            }
        }

        // dp[i][j] 表示前 i 个房子(houses[0] ~ houses[i - 1]), 用了 j 个邮筒,距离和的最小值
        int[][] dp = new int[n + 1][k + 1];

        // 初始化 dp[1][1] ~ dp[n][1], dp[1][2] ~ dp[1][k]  
        for (int i = 1; i <= n; i++) {
            dp[i][1] = rec[1][i]; // 从第一个房子到第 i 个房子
        }
        for (int j = 2; j <= k; j++) {
            dp[1][j] = Integer.MAX_VALUE;
        }

        // dp[i][j] = min{dp[x][j - 1] + rec[x + 1][i] | j - 1 <= x <= i - 1}
        // x = j - 1 表示前 j - 1 个房子各对应一个邮筒, 第 j 个房子到第 i 个房子对应第 j 个邮筒
        // x = i - 1 表示前 i - 1 个房子对应前 j - 1 个邮筒,第 i 个房子对应第 j 个邮筒
        for (int i = 2; i <= n; i++) {
            for (int j = 2; j <= k && j <= i; j++) {
                dp[i][j] = Integer.MAX_VALUE;
                for (int x = j - 1; x <= i - 1; x++) {
                    dp[i][j] = Math.min(dp[x][j - 1] + rec[x + 1][i], dp[i][j]);
                }
            }
        }
        return dp[n][k];
    }
}

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值