给你一个高度数组Hi,每提升Hi一个单位的代价是Wi,求让相邻两个高度不同的最小代价。

一.题目描述

给你一个高度数组Hi,每提升Hi一个单位的代价是Wi,求让相邻两个高度不同的最小代价。比如Hi [2,3,4,4];Wi [1, 2,3,4],就是让第三个提升1。

二.解题思路

一开始我的想法是:当 H [ i ] H[i] H[i] != H [ i − 1 ] H[i-1] H[i1]时,我们不需要对第 i i i列进行增高,只有当 H [ i ] H[i] H[i]= H [ i − 1 ] H[i-1] H[i1]时,我们才需要提高第 i i i或第 i − 1 i-1 i1列。但是如何选取提高哪列呢,并且该提高多少高度呢,这些都让我产生了困扰,那我们可不可以把提高第 i i i列和第 i − 1 i-1 i1列的情况都考虑呢,并且把可能提高的所有高度都考虑到(可能提高1或2…或n)。

很显然,我们提高的高度是有限制的,超过这个限制再进行提高只会白白增加代价,那么这个限制是多少呢?

我们假设数组 H H H的长度为 n n n,最大元素为 h m a x h_{max} hmax,我们为了满足使相邻两个高度不同这个条件,对原数组 H H H中的元素进行提升,那么提升后的数组 H ′ H' H的最大高度最多为 h m a x + 1 h_{max}+1 hmax+1

证明如下: 我们可以将原始数组 H H H中的所有元素提升到 h m a x h_{max} hmax,然后间隔着再提升一个单位即可,得到: h m a x , h m a x + 1 , h m a x , h m a x + 1 , h m a x … … h_{max},h_{max}+1,h_{max},h_{max}+1,h_{max}…… hmax,hmax+1,hmax,hmax+1,hmax……这样的序列,就可以满足相邻两个高度不同了,所以提升后满足条件的数组的最大高度就是 h m a x + 1 h_{max}+1 hmax+1

那么我们可以构建这样一个dp数组:

//n为数组长度,h_max+2表示高度0~h_max+1(最大高度)
int[][] dp = new int[n][h_max+2]; 

其中 d p [ i ] [ j ] dp[i][j] dp[i][j]表示计算到第 i i i列为止(即只看前 i i i列),第 i i i列的高度提升为 j j j的最小代价。

那么我们怎么去更新它呢?

对于第0列,我们很好去更新,因为不用考虑与前面的列高度相同,更新公式如下:

//第0列更新代价=它自身提升高度所需要的代价
//其中W[0]为第0列提升一个单位的代价
//高度j不能小于H[0],因为只能提升,不能减小
for(int j = H[0]; j <= h_max+1; j++)
	dp[0][j] = (j-H[0])*W[0];

对于第1列,我们就需要考虑它与第0列高度不同这个条件了,因此更新过程如下:对于 d p [ 1 ] [ j ] ( j > = H [ 1 ] ) dp[1][j](j >= H[1]) dp[1][j](j>=H[1]),它的总代价由两部分组成:一部分是它自己从 H [ 1 ] H[1] H[1]提升至 j j j的代价,这部分代价为 ( j − H [ 1 ] ) ∗ W [ 1 ] (j-H[1])*W[1] (jH[1])W[1];另一部分代价是从第0列继承的代价,这一部分的最小值为 m i n ( d p [ 0 ] [ k ] ) ( k ∈ [ H [ 0 ] , h m a x + 1 ] 且 k ≠ j ) min(dp[0][k])(k\in[H[0], h_{max}+1]且k\ne j) min(dp[0][k])(k[H[0],hmax+1]k=j) k ≠ j k\ne j k=j是因为要保证相邻两个的高度不同。更新公式如下:

int min_pre_cost = 0x3f3f3f3f;
// 找到前一个dp[i-1]列的最小cost,并且不能与当前的高度j相同
for(int k = H[i-1]; k < m; k++){
	if(k == j) continue;
	if(dp[i-1][k] < min_pre_cost) min_pre_cost = dp[i-1][k];
}
dp[i][j] = min_pre_cost + (j-H[i])*W[i];

这样我们就可以解决之前的疑问:如果 H [ i ] = H [ i − 1 ] H[i]=H[i-1] H[i]=H[i1],是提升第 i i i列还是提升第 i − 1 i-1 i1列,提升多少高度;因为我们已经考虑了所有情况。

最终的最小代价就是 m i n ( d p [ n − 1 ] ) min(dp[n-1]) min(dp[n1])

三.示例

在这里插入图片描述
黄色部分代表原始数组高度 H = [ 2 , 3 , 4 , 4 ] H=[2, 3, 4, 4] H=[2,3,4,4];白色部分为dp数组更新的部分;加号前面的代表当前列提升的代价,后面代表继承前一列的代价

其中最后一列的第二行从前一列的继承代价不为0,而是w[2]*1,这是因为前一列代价为0的那个格子高度和它一样,为满足高度不同的条件只能继承第三列第一行的那个w[2]*1+0。

四.完整代码
public int minCost(int[] H, int[] W){
   int n = H.length;
   int h_max = 0;
   for(int h: H)
       if(h > h_max)
           h_max = h;

   int m = h_max+2;
   // dp[i][j]表示计算到第i个的高度为j的cost
   int[][] dp = new int[n][m];
   for(int i = 0; i < n; i++)
       for(int j = 0; j < m; j++)
           dp[i][j] = -1;

   for(int i = 0; i < n; i++){
       //遍历当前第j列的可能高度
       for(int j = H[i]; j < m; j++){
           //遍历前一个dp[i-1]
           if(i == 0){
               dp[i][j] = (j-H[i])*W[i];
           }else{
               int min_pre_cost = 0x3f3f3f3f;
               // 找到前一个dp[i-1]列的最小cost,并且不能与当前的高度j相同
               for(int k = H[i-1]; k < m; k++){
                   if(k == j) continue;
                   if(dp[i-1][k] < min_pre_cost) min_pre_cost = dp[i-1][k];
               }
               dp[i][j] = min_pre_cost + (j-H[i])*W[i];
           }
       }
   }

   int res = 0x3f3f3f3f;
   //第n-1列的最小值即为最终结果
   for(int i = H[n-1]; i < m; i++)
       if(dp[n-1][i] < res)
           res = dp[n-1][i];

   return res;
}

时间复杂度O(n³)

五.优化为O(n²)

因为一共要更新O(n²)个dp[i][j],所以最小复杂度是O(n²)。上面代码中找前一列的最小值的部分可以优化:当我们更新 d p [ i ] dp[i] dp[i]这一行时,我们可以保留前一行 d p [ i − 1 ] dp[i-1] dp[i1]的最小的值 m i n 1 min1 min1和次小的值 m i n 2 min2 min2。当每次更新 d p [ i ] [ j ] dp[i][j] dp[i][j]时,原来我们需要遍历 d p [ i − 1 ] dp[i-1] dp[i1]一整行,复杂度O(n);但现在我们只需要判断前一列高度相同的 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]是否等于 m i n 1 min1 min1,如果相等,那么前一列继承的最小代价为 m i n 2 min2 min2,否则为 m i n 1 min1 min1,复杂度O(1)。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值