前言

当前所有算法都使用测试用例运行过,但是不保证100%的测试用例,如果存在问题务必联系批评指正~

问题介绍

原问题
给定一个正整数数组和一个num,其中arr[i]代表第i个居民所在的一维空间的位置,如arr=[1,2,3,4,5,1000],代表arr[4] 在位置4,arr[5]在位置1000,那么arr[4]和arr[5]之间的距离为1000-5 = 995
num为邮局的数量,现在邮局需要放置在若干居民的位置上,求如何放置邮局位置才能使的所有居民到邮局的距离之和最短?

解决方案

暴力递归

动态规划
动态规划之前,我们先计算出来一个二维数组w,其中w[i][j]代表arr[i…j]由一个邮局来解决时,最终的最短距离,如何求w?
w[i][j] = w[i-1][j] + arr[j] - arr[(i+j)/2]
要素一:dp[i][j]代表 i个画匠完成 arr[0…j]个居民位置所产生的最短距离
要素二:dp[i][j] 的计算方式:
· 游标k从j到0,依次代表 arr[k…j]由第一个邮局负责,剩下的arr[0…k]由剩下的i-1个邮局完成,求所有可能性中的最小值即可
四边形不等式优化动态规划
在原动态规划基础上,限制第三层循环尝试的x的范围:
·如果dp[i-1][j]获取到最少的距离,此时k值记录为l,后面计算dp[i][j]时,k值将不再小于l
·如果dp[i][j+1]获取到最少的距离,此时k值记录为r,后面计算dp[i][j]时,k值将不再大于r
根据以上两点可以确定尝试x的范围在[l…r],具体四边形不等式的证明非常多,这里不再证明

代码编写

java语言版本

基本动态规划:

   /**
     * 二轮测试:动态规划解法
     * @return
     */
    public static int solution1Cp1(int[] arr, int num) {
        if (arr == null || num == 0) {
            return -1;
        }
        // wArr[i][j] 表示 arr[i...j]中只放一个邮局时需要的最短距离数
        int[][] wArr = getWArr(arr);
        // dp[i][j]代表i个邮局解决 arr[0..j]的居民问题
        int[][] dp = new int[num + 1][arr.length];
        // 初始化dp
        dp[1][0] = 0;
        for (int j = 1; j < dp[0].length; j++) {
            dp[1][j] = wArr[0][j];
        }
        // 动态规划主体
        for (int i = 2; i < dp.length; i++) {
            for (int j = 0; j < dp[0].length; j++) {
                if (j <= i) {
                    // 每一个居民都能分到邮局,直接没有距离
                    dp[i][j] = 0;
                    continue;
                }
                int min = Integer.MAX_VALUE;
                // 第一个邮局负责arr[k...j],剩下的负责arr[0..k-1]
                for (int k = j; k >= 1; k--) {
                    // 第一个邮局解决的最短距离
                    int first = wArr[k][j];
                    int second = dp[i-1][k-1];
                    min = Math.min(min, first+second);
                }
                dp[i][j] = min;
            }
        }
        return dp[num][arr.length-1];
    }

    /**
     * wArr[i][j] 表示 arr[i...j]中只放一个邮局时需要的最短距离数
     * @param arr
     * @return
     */
    private static int[][] getWArr(int[] arr) {
        int[][] wArr = new int[arr.length][arr.length];
        // 只需要填充上半部分
        for (int i = 0; i < arr.length; i++) {
            for (int j = i+1; j < arr.length; j++) {
                // 这里需要解释一下,中点因为两边的元素数目相同(偶数特殊,但也相同),滑动时保持两边数相同时能够保证总合不变
                wArr[i][j] = wArr[i][j-1] + arr[j] - arr[(i+j)/2];
            }
        }
        return wArr;
    }

四边形不等式优化动态规划

       /**
     * 二轮测试:四边形不等式
     * @return
     */
    public static int solution2Cp2(int[] arr, int num) {
        if (arr == null || num == 0) {
            return -1;
        }
        // wArr[i][j] 表示 arr[i...j]中只放一个邮局时需要的最短距离数
        int[][] wArr = getWArr(arr);
        // dp[i][j]代表i个邮局解决 arr[0..j]的居民问题的最短距离
        int[][] dp = new int[num + 1][arr.length];
        // map[i][j]代表i个邮局解决 arr[0..j]的居民问题最优解时的k值
        int[][] map = new int[num + 1][arr.length];
        // 初始化dp
        dp[1][0] = 0;
        for (int j = 1; j < dp[0].length; j++) {
            dp[1][j] = wArr[0][j];
        }
        // 动态规划主体
        for (int i = 2; i < dp.length; i++) {
            for (int j = 0; j < dp[0].length; j++) {
                if (j <= i) {
                    // 每一个居民都能分到邮局,直接没有距离
                    dp[i][j] = 0;
                    continue;
                }
                int min = Integer.MAX_VALUE;
                // 左边至少给一个居民让给剩下的邮局,因为邮局至少有两个
                int l = map[i-1][j] == 0 ? 1 : map[i-1][j];
                int r = j == dp[0].length-1 ? dp[0].length-1 : map[i][j+1];
                // 第一个邮局负责arr[k...j],剩下的负责arr[0..k-1]
                for (int k = r; k >= l; k--) {
                    // 第一个邮局解决的最短距离
                    int first = wArr[k][j];
                    int second = dp[i-1][k-1];
                    if (first + second < min) {
                        min = first+second;
                        map[i][j] = k;
                    }
                }
                dp[i][j] = min;
            }
        }
        return dp[num][arr.length-1];
    }

c语言版本

正在学习中

c++语言版本

正在学习中

思考感悟

有兴趣可以对比一下画匠问题,发现我的解法和画匠问题一模一样的思路,我自己都觉得这篇文章有点水了哈哈
这道题跟画匠问题有一个差别就是w矩阵的计算,这个计算方式是怎么来的?
比如arr = [1,2,3,4,5,8,10],毫无疑问4的位置可以让所有的位置之和最终最短,
我们可以发现如果从4移动到5,整体的距离之和会发生什么变化?
1原本距离4为3,现在距离多了一个1
2多了一个1
3多了一个1
4原本是9,现在也多了一个1

5原本是1,现在少了一个1
8原来是4,现在是3,少了一个1
10原来是6,现在是5,少了一个1
因此整体变化量是多了一个1,因为现在k的位置已经不在中点了
那么现在如果多一个位置12,整体就是[1,2,3,4,5,8,10,12],我们会发现如果原本的4到5多出来的那个1其实会被12的出现”中和掉“,好好体会
那么现在的整体[1,2,3,4,5,8,10,12]的最短距离应该由“[1,2,3,4,5,8,10]的最短距离” 和 “12这个多出来的数字-中点” 这两个数字组成,还是很巧妙的。

写在最后

06-27 11:02