arr代表居民位置,想建k个邮局,则所有居民到k个邮局之间的距离之和最小是?

arr代表居民位置,想建k个邮局,则所有居民到k个邮局之间的距离之和最小是?

提示:本题是经典的四边形不等式优化枚举的题目!非常困难

关于四边形不等式的优化,我暂时也就写这一题,然后后面有时间我再多讲几个
重要的是要把互联网大厂的基础算法原型搞清楚,突破了简单一点的那些题目,然后才能攻克这些动态规划难题。
考试的时候一般就2小时,可能都没有时间来写这些题目了。


题目

arr代表整个x轴上,每一户居民家的位置,现在想在这些居民点建k个邮局,请问,所有居民到这k个邮局之间的最小距离是多少?


一、审题

示例:arr=3 7 15,k=1
整个范围上建k=1个邮局
自然是建在中点处
则7–7的距离,0
+3–7的距离,4
+15–7的距离,8
=12
这就是最小距离
在这里插入图片描述
你当然可以将邮局建在3或者15那
比如建在3上,那d就是16,显然不是最小距离
在这里插入图片描述


二、解题

看了实例就明白了,想让arr整个0–N-1范围上建立k个邮局,要求arr到负责他们各自的邮局的距离最短,然后求整个最短距离的和的最小值。

这个题显然就是一个动态规划题目!
这种2个变动的变量,就是样本位置对应模型,0–i范围内,建j个邮局,求居民们到k个邮局的最小距离和定义为:dp[i][j]
最终咱们取结果:dp[N-1][k], 即整个0–N-1范围上建立k个邮局,要求arr到负责他们各自的邮局的距离最短,最短距离的和的最小值就是dp[N-1][k]

需要填一个表格:
(1)第0列,咱不需要填写,因为你j=0,就是建0个邮局,最小距离则为无穷大。
(2)第0行,全是0,为啥呢?因为你在0位置建1,2,3–k个邮局,全部只能建在0位置上,所以距离就是0,没啥多说的
在这里插入图片描述
(3)第1列,这里比较特殊:
第一列的每一行叫dp[i][1]
啥意思呢?就是0–i范围内建1个邮局来负责所有的居民,那这最短距离是多少呢?
我们来推一下,很简单,实际上就是i/2位置处,就是最好的建址:
i=1:在arr=0 1上建1个邮局,你往0上建,1上建,一样,dp[1][1]=0+arr[1]-arr[0];
i=2:在arr=0 1 2上建1个邮局,上面求过了0 1的情况,现在只考虑2,其实我们把0 1的邮局当建在1也是一样的,刚刚说过
所以呢,此时就建在1上,对于1 2来说,就是最短的,dp[2][1]=dp[1][1]+arr[2]-arr[1];
i=3:在arr=0 1 2 3上建1个邮局,显然,在1和2上任意一个位置建,都一样,不妨去上中位数位置i/2=3/2=1,在1上建的话
只需要考虑3新带来的距离:dp[3][1]=dp[2][1]+arr[3]-arr[1];
……
一样,总结一下,新来一个i,直接让dp[i][1]=dp[i-1][1]+arr[i]-arr[i/2]; ,这里是0–i范围内,用1个邮局负责得到的最短距离
因此,我们完全可以利用一个数组,提前把这个算出来,然后一会以o(1)速度调用

定义:record[L][R]代表啥呢?
代表在L和R的中点建1个邮局,负责L–R范围上的居民,居民到邮局的最短距离和
根据上面的0–i范围内的推论,L–R范围内的最短距离和是这样求的
即:record[L][R] = record[L][R - 1] + arr[R] - arr[(L + R) >> 1];

public static int[][] getRecord(int[] arr){
        int N = arr.length;
        int[][] record = new int[N][N];

        //主对角线已经是000,不管了
        //从i==0,j==i+1开始填表,才能保证左下角不用填写
        for (int L = 0; L < N; L++) {
            for (int R = L + 1; R < N; R++) {//起点是L+1,副对角那
                record[L][R] = record[L][R - 1] + arr[R] - arr[(L + R) >> 1];//补中点的距离就行了,这就是最短的距离,没啥可说的
            }
        }

        return record;
    }

那么自然:dp[i][1] = record[0][i] ;0–i范围内建一个邮局,得到的最短距离和

好,来看任意位置格子dp[i][j]怎么填?
(4)dp[i][j]咋求?
再把上面那图复制下来,咱看着表解释:
样本位置对应模型:一个样本i变量做行,另一个样本j做列,自然这么思考,考虑i做开头,或者结尾,此处,咱看i处做结尾的情况
我们说了,dp[i][j]是0–i范围内建j个邮局,得到的最短距离和
所以,我们来枚举下列所有的情况:
dp[i][j] = min(下面的所有可能性)
——在0–i-1范围内由j-1个邮局负责,而i–i范围内单独由1个邮局负责(这算是0–i范围内由j个邮局负责);d=dp[i-1][j-1]+record[i][i];
——在0–i-2范围内由j-1个邮局负责,而i-1–i范围内单独由1个邮局负责;d=dp[i-2][j-1]+record[i-1][i];
——在0–i-3范围内由j-1个邮局负责,而i-2–i范围内单独由1个邮局负责;d=dp[i-3][j-1]+record[i-2][i];
……
——在0–0范围内由j-1个邮局负责,而1–i范围内单独由1个邮局负责;d=dp[i-i][j-1]+record[i-i+1][i];
——j-1个邮局咱都认为坏了,不建了,而0–i范围内单独由1个邮局负责d=0+record[i-i][i];
在这里插入图片描述
观察一下,整个dp[i][j] 依赖左边j-1列,所有0–i行那些格子
因此,我们可以把代码这么写:

//有了record,我们就能填dp了,这样操作为o(1)
    //dpij填写是o(n^2)
    public static int minDistancePostOffice(int[] arr, int k){
        if (arr == null || arr.length == 0 || k < 0) return 0;

        int N = arr.length;
        int[][] dp = new int[N][k + 1];//因为j==0---k,k+1个取值
        int[][] record = getRecord(arr);//拿到一个范围上只有一个邮局的处理结果,最短距离

        //第0列咱不用,不管
        //第0行,因为就一个用户,你用的邮局数量>用户数量,自然就是距离为0,天然就是
        //第1列,1个邮局,负责0--i号用户的情况,那自然那record就行呗
        for (int i = 0; i < N; i++) {
            dp[i][1] = record[0][i];
        }
        //然后任意位置ij,j==2开始
        for (int i = 0; i < N; i++) {
            for (int j = 2; j <= Math.min(k, i); j++) {
                //实际的填表时,发现,如果i+1个用户数量<=j个邮局,那结果必然是0
                //所以当j超过了i+1就算了,而且j最多也只能取到k,没那么多
                dp[i][j] = record[0][i];//枚举的最后一个情况,让0--i全部由一个邮局负责,其他j-1个邮局啥也不干……
                //然后对比其他的枚举情况,取最小值,t号居民
                for (int t = i; t > 0; t--) {
                    dp[i][j] = Math.min(dp[i][j], record[t][i] + dp[t - 1][j - 1]);
                    //枚举我1一个邮局负责t--i,最小距离d1,
                    // 然后j-1个邮局负责0--t-1这些居民,d2,
                    // dpij==d1+d2
                }
            }
        }

        return dp[N - 1][k];//返回整个居民范围上,建k个邮局能达到的最短距离
    }

——解释一下代码中:j <= Math.min(k, i);
这里啥意思呢?为啥j要小于k和i的最小值,也就是说j>=i的不用填了,距离为0
j>i也即j。=i+1说明什么问题,j是邮局的个数,i+1是居民的个数,当邮局数量>=居民数量时,完全可以一个居民一个邮局,整个距离就是0,何必再填表格呢?懂了吧。
——开始枚举之前,首先让距离为0–i范围内全部让1个邮局负责,也就是枚举行为中的最后一种情况,这个距离一般来说都是很大的,在枚举其他情况时,会有更好更小的距离,因此最开始枚举前直接让:dp[i][j] = record[0][i];
——然后枚举其他情况,让j-1个邮局负责0–t-1范围,让1个邮局负责t–i范围,t从i开始枚举,最后t最小为1,【0的话就是dp[i][j] = record[0][i];刚刚已经放前面了】势必会有更小的距离和,更新给 dp[i][j];

//然后对比其他的枚举情况,取最小值,t号居民
                for (int t = i; t > 0; t--) {
                    dp[i][j] = Math.min(dp[i][j], dp[t - 1][j - 1] + record[t][i]);
                    // 然后j-1个邮局负责0--t-1这些居民,d2,
                    //枚举我1一个邮局负责t--i,最小距离d1,
                    // dpij==d1+d2
                }

测试代码:

public static void test(){
        int[] arr = {3,18,105,877,987,1003};
        int k = 3;
        System.out.println(minDistancePostOffice(arr, k));
    }


    public static void main(String[] args) {
        test();
    }

下面的可以不看,我也没搞懂,后面有机会再搞

好,上面的时间复杂度,我们可以算一下
填表o(n^2) * o(k)枚举
显然复杂度还是很高的

而本题是需要优化的,恰是四边形不等式优化【这就是个名字,没啥特殊含义】,目的是省掉很多枚举行为
所谓四边形不等式,有这么几个特征:
(1)观察dp[i][j],当固定j时,如果增大i,也就是说同样的邮局j,要负责更大范围内的邮局,整个dp[i][j]距离和必然会增大;——单调性
(2)观察dp[i][j],当固定i时,如果增大j,也就是说同样的居民数量,邮局变多了,那自然整个dp[i][j]距离和会变小的;——单调性
(3)dp[i][j]往往是区间划分问题,就像上面划分枚举0–t-1,t–i俩区间去放邮局这样。
(4)dp[i][j]不同时依赖i行和j列【即,要么i行j列都不依赖,要么最依赖i行,要么只依赖j列,决不能同时依赖i行和j列】
比如本题,只依赖i行j-1列那个格子和j-1列其他格子,但是不同时依赖行和j列。
则完全可以把枚举行为限制在a<=t<=b之间
a和b什么意思:
如果满足上面四个特征,这就是四边形不等式的优化,枚举行为t,可以限制在上方和右边的位置……
别看了,我也没搞懂………………
今后有机会,我理解清楚再来搞这个四边形不等式优化。
在这里插入图片描述


总结

提示:重要经验:

1)四边形不等式的优化,目的是省掉枚举行为,但是不懂没关系
2)就暂时先懂本题用动态规划怎么求解就行

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰露可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值