leetcode
leetcode 1351 ~ 1400
安排邮筒

安排邮筒

难度:

标签:

题目描述

给你一个房屋数组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 中的整数互不相同。

代码结果

运行时间: 73 ms, 内存: 16.8 MB


/*
 * 问题描述:给定一组房屋位置和k个邮筒,找出将邮筒放置在街道上的位置,使得每栋房子到最近邮筒的距离之和最小。
 * 
 * 思路:
 * 1. 由于Java Stream API不能直接用于复杂的动态规划问题,所以我们使用stream对数组进行排序和计算距离。
 */

import java.util.Arrays;
import java.util.stream.IntStream;

public class MinDistanceToMailboxStream {
    public int minDistance(int[] houses, int k) {
        Arrays.sort(houses);
        int n = houses.length;
        int[][] dp = new int[n + 1][k + 1];
        int[][] cost = new int[n + 1][n + 1];

        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                int median = houses[(i + j) / 2];
                final int left = i;
                final int right = j;
                cost[i + 1][j + 1] = IntStream.rangeClosed(i, j)
                        .map(idx -> Math.abs(houses[idx] - median))
                        .sum();
            }
        }

        IntStream.range(1, n + 1).forEach(i -> Arrays.fill(dp[i], Integer.MAX_VALUE / 2));
        dp[0][0] = 0;

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= k; j++) {
                for (int m = 0; m < i; m++) {
                    dp[i][j] = Math.min(dp[i][j], dp[m][j - 1] + cost[m + 1][i]);
                }
            }
        }

        return dp[n][k];
    }
}

解释

方法:

这个问题可以通过动态规划来解决。首先,我们对房屋位置进行排序,使得问题变得更容易处理。定义dp[i][j]表示前i+1个房屋安排j个邮筒的最小距离总和。数组one[i][j]用于存储从i到j的房子只用一个邮筒时的最小总距离,其中邮筒放在中位数位置能够达到最小距离。我们首先计算所有可能的one[i][j]值。然后,使用动态规划填充dp数组。对于每个dp[i][j],我们尝试所有可能的前一个状态dp[k][j-1](其中k < i),并加上从k+1到i的房屋使用一个邮筒的距离,即one[k+1][i]。通过这种方式,我们可以构建出所有房屋和邮筒配置的最小距离总和。

时间复杂度:

O(n^2 * k)

空间复杂度:

O(n^2 + n * k)

代码细节讲解

🦆
为什么在解题中首先需要对房屋的位置进行排序?排序的目的和效果是什么?
对房屋位置进行排序的目的是为了简化计算和优化算法的实现。在已排序的房屋位置列表中,任意子区间[i, j](i <= j)的房屋都是连续的,并且位置是递增的。这样便于计算这些房屋到一个邮筒的最短距离,因为邮筒放在中位数位置时能达到最小总距离,而中位数很容易从排序后的列表中得到。此外,排序后的列表也便于使用动态规划方法计算不同房屋分组的最小距离,因为每次分割都是基于连续的子序列。
🦆
在计算one数组时,为什么选择邮筒放在中位数位置能达到最小总距离,这个逻辑是如何得出的?
当邮筒放在一组房屋的中位数位置时,可以最小化到所有房屋的距离总和。这是因为中位数的特性是它将数据集分成两个数量大致相等的部分,从而使得左边和右边房屋到邮筒的距离总和达到最小。具体来说,将邮筒置于中位数位置,对于中位数左侧的每一步向左移动,左侧房屋到邮筒的距离会增加,但右侧的会减少;由于右侧房屋数量不少于左侧,总距离因此减少。同理,向右移动邮筒也遵循类似的逻辑。因此,中位数是最优位置。
🦆
one数组中的p1和p2的计算公式是如何推导出来的,能否详细解释一下它们的含义?
在计算one数组时,p1和p2的计算关键在于确定中位数房屋左侧和右侧房屋到邮筒的距离总和。具体来说,p1计算的是从i到m(中位数)房屋的距离总和,p2计算的是从m+1到j房屋的距离总和。公式中,(m - i + 1) * houses[m] 表示中位数到所有左侧房屋(包括中位数本身)的距离乘以房屋数量,pre数组用于快速计算区间和,即pre[m+1] - pre[i]是i到m的房屋位置和。同理,p2中的pre[j+1] - pre[m+1]表示m+1到j的房屋位置和,(j - m) * houses[m]则是这些房屋到中位数的距离乘以房屋数量。这样,p1 + p2就给出了所有房屋到中位数邮筒的总距离。

相关问题