• 算法总结-最短距离和问题


    第一种:纯种最短距离和

    形式1:在坐标轴上有一些点,你可以在其中任意一点放置某种东西,求放置点到所有点的最小和

    形式2:将所有值改成相等的某个值,每次修改可以进行加1或者减1,求最少操作数

    上图源自力扣462

    这种距离总和问题的统一解法,是吧这个 相等值/坐标位置 放置在中间数上。

     为什么放中间数距离和最小呢?

    假设四个点,放在最左侧,第一段线段计算3次,第二段计算2次,第三段计算1次

    放中间的话:

    则两侧计数都会减少 

    四个数的情况,放左右两侧效果相同,中间一段都算了两次,也就是偶数放在中间任意位置的一个可以使得距离和最小

    奇数情况,放中间即可,上下两部分距离是相等的,因此也可以双指针计算差值的和 

    1. class Solution {
    2. public int minMoves2(int[] nums) {
    3. Arrays.sort(nums);
    4. int ans = 0;
    5. int n = nums.length;
    6. for(int i = 0,j=n-1; i < j; i++,j--){
    7. ans += nums[j]-nums[i];
    8. }
    9. return ans;
    10. }
    11. }

    第二种:加权最短距离和

    力扣-2448

    有权值的情况,我们可以按数值变化修改两侧水位线求解。

    1. 假设都降到最低值,我们可以计算出一个开销 rightSum,右侧开销总和rightCostSum=XX,以及rightLen=n,左侧开销leftSum=0,以及左侧长度leftLen=0,leftCostSum=0

    2. 那么此时我们往右移会发生什么呢?

            左侧长度为 leftLen 个元素,每个增加了 v[i]-v[i-1],每一份的开销都会消耗 leftCostSum 的总开销(rightSum为对应方向所有cost之和),总计增加消耗 (v[i]-v[i-1])*(leftCostSum)

            右侧长度为 rightLen 个元素,每个减少了 v[i]-v[i-1],每一份的开销都会减少 rightCostSum,总计减少消耗 (v[i]-v[i-1])*(rightCostSum)

    3. 通过这个移动水位线的方式,我们可以计算完成所有位置的消耗

    1. class Solution {
    2. public long minCost(int[] nums, int[] cost) {
    3. int n = nums.length;
    4. Integer[] sort = new Integer[n];
    5. for(int i = 0; i < n; i++) sort[i] = i;
    6. Arrays.sort(sort,(a,b)->nums[a]-nums[b]);
    7. int min = nums[sort[0]];
    8. int max = nums[sort[n-1]];
    9. long leftSum = 0;
    10. long rightSum = 0L;
    11. for(long c:cost) rightSum += c;
    12. long curr=0;
    13. for(int i = 0; i < n; i++){
    14. curr += ((long)nums[i]-min)*(long)cost[i];
    15. }
    16. long ans = curr;
    17. for(int i = min+1,j=0; i <= max; i++){
    18. while (j
    19. leftSum+=cost[sort[j]];
    20. rightSum-=cost[sort[j]];
    21. ++j;
    22. }
    23. curr = curr-rightSum+leftSum;
    24. ans = Math.min(ans,curr);
    25. }
    26. return ans;
    27. }
    28. }

     第三种:步长变换

     

    力扣-2033

    这题不一样的地方是多了一个步长,其实这个二维没有意义,当成一维去解就可以了,不同的是,步长会多了一个判断能否成功,比如步长是2,一个1一个2必然无法相等,所以这题需要增加一个同余的判断,其他与第一题相同

    第四种,割补法

     力扣-1684

    1. 需要把1的位置转换为实际距离0点的距离,然后求得值就比较接近最小距离和了,我们先 把它当成最小距离和去求,那么完全可以采用类似第二种的处理方式,得到最小距离和

    2. 偏差,左右两侧可以使用左割右补的方式,把同一位置改为相邻位置

    1. 简化问题。假设我们不限制连续K个。

      • 那么实际上,本题可看做是1的位置距离坐标轴起点的各个值,中间放置一点加油站,形成距离和的最小值问题
      • 多点求最短距离和,最佳方式是放在中位数上
      • 因此我们可以先把本题换成1距离坐标轴起点的位置,也就是索引值
    2. 限制长度K,就是长度 N 上,我们只需要其中 K 个节点的连续值。那么我们可以考虑 从 MID(MID是K/2) 到 N-MID2(MID2是K-K/2) 上面的所有值

    3. 计算次数

      • 首先图中的长条代表面积,前缀和代表当前长条和上面所有长条的距离之和
      • 有前缀和就意味着可以计算任意一行到后面某一行的面积和
      • 假设以红色条4为核心,则我们需要的面积是 红色长条*左半边长度MID(MID是K/2)-(左半边前缀和差)-等差数列。为什么要减掉个等差数列呢?因为我们计算过程中,实际上是把左边所有元素平移到4的位置。实际是对于前一个元素,只需要移动到3,再前一个只需要移动到2,逐步递减,所以就形成了一个需要减掉的等差数列
      • 对于右侧元素,我们需要的面积是 右侧前缀和-红色长条*MID2-等差数列2,如黄色部分,是需要减掉的等差数列。
      • 最终在红色长条上下的蓝色部分,即为答案
      • 枚举所有中心节点获得最优解
     
    
    1. class Solution {
    2. public int minMoves(int[] nums, int k) {
    3. int n = nums.length;
    4. int sum = 0;
    5. for(int num:nums) sum += num;
    6. long[] arr = new long[sum];
    7. for(int i =0,j=0;i
    8. if(nums[i]==1) arr[j++]=i;
    9. }
    10. for(int i =1;i
    11. arr[i] = arr[i]+arr[i-1];
    12. }
    13. long ans = Long.MAX_VALUE;
    14. int half1 = (k+1)/2;
    15. int half2 = k-half1;
    16. for(int i = half1-1; i < arr.length-half2;i++){
    17. long iPre = (i-1<0?0:arr[i-1]);
    18. long halfPre = i-half1<0?0:arr[i-half1];
    19. long leftSum = (arr[i]-iPre)*(long)(half1)-(arr[i]-halfPre);
    20. long rightSum = arr[i+half2]-iPre-(arr[i]-iPre)*(half2+1);
    21. long leftCost = leftSum-((long)(half1)*(half1-1)/2);
    22. long rightCost = rightSum-((long)(half2)*(half2+1)/2);
    23. ans = Math.min(ans,leftCost+rightCost);
    24. }
    25. return (int) ans;
    26. }
    27. }

    时间复杂度:O(N+M),N为原数组长度,M为1的个数
    空间复杂度:O(M)

     第五种,放置K个点最短距离和

    通过第一种情况,我们知道,如果确定了范围,距离一个点的最短距离和,最佳放置方式是把它放中间。那么对于K个点的情况,我们只要枚举前一个部分的分割点即可。特别地,第一个的点的放置的起始位置只能是开头。

    1. 假设一组房子拥有一个邮筒,则邮筒,应该尽可能地放在房子中间
      • 奇数放在中间
      • 偶数放在中间两个的任意一个位置
    2. 于是想到动态规划,以某点为终点,消耗几个邮筒对应的最小距离
    3. 枚举邮筒个数,以及前一个邮筒范围的结束位置,在上一个邮筒位置的基础上,把当前邮筒放在这段房子的中间,比较最小距离
    1. class Solution {
    2. public int minDistance(int[] houses, int k) {
    3. Arrays.sort(houses);
    4. int n =houses.length;
    5. int[][] dp = new int[n][k+1];
    6. for(int i = 0; i < n; i++){
    7. Arrays.fill(dp[i],Integer.MAX_VALUE/2);
    8. dp[i][0] = 0;
    9. }
    10. for(int i=0; i < n; i++){
    11. for(int j = 1; j <= Math.min(i+1,k); j++){
    12. for(int t = 0; t <= i;t++){
    13. dp[i][j]=Math.min((t==0?0:dp[t-1][j-1])+getDis(houses,t,i),dp[i][j]);
    14. if(j==1) break;
    15. }
    16. }
    17. }
    18. return dp[n-1][k];
    19. }
    20. private int getDis(int[] houses, int i, int j){
    21. int ans = 0;
    22. while(i
    23. ans += houses[j--]-houses[i++];
    24. }
    25. return ans;
    26. }
    27. }

    时间复杂度:O(n^2*k+nlogn)
    空间复杂度:O(nk)

  • 相关阅读:
    何为AutoML
    UE5 GAS 学习笔记 10.3 LyraStarter案例解析(中)
    用户数据权利请求响应
    全自动生成搞笑聊天视频,一键制作搞笑对话视频
    文件上传绕过最新版安全狗
    ThreadLocal InheritableThreadLocal TransmittableThreadLocal简单使用
    R数据分析:纵向分类结局的分析-马尔可夫多态模型的理解与实操
    低代码开发平台赋能智慧警务管理:创新引领下的安全新篇章
    解决国产机SVN连接失败的问题
    c语言的数据结构:队列
  • 原文地址:https://blog.csdn.net/yu_duan_hun/article/details/127735138