[Leetcode] 265. Paint House II

这一题其实不是很难。首先,很明显这是一道dp题。dp的方程式也不难推出
如果说f(i, j)是在第i个房间涂第j种颜色的最优解,那么方程式就是

f(i, j) = costs(i, j) + min(f(i - 1, x)) 其中x 是(0, 1, 2, 3, ... k)中除去 j的集合(因为不能相邻房间不能同色)。

那么很显而易见,走完n个房子k种颜色,需要的复杂度是O(nk^2),主要是 min(f(i - 1, x))的操作是O(k)的。所以这个Follow up的关键就是把这个O(k)的操作变成O(1)的。首先我们需要一个O(k)的空间去装当前这一行正在计算的f(i, j),我们称之为curLine

其中一个次优解是用到一个多余的O(k)的空间的,我们叫它为prevMin。就是用一个大小为k的数组去记录每一个位置上的,min(f(i - 1, x))。可以看得出prevMin是通过curLine得到的,也就是计算第i + 1个house的k种颜色的情况的时候。prevMin是通过第i个house计算得出的curLine得到的。 做法可以是这样:当我们计算出curLine在第i个house的解集之后,正反各扫一遍curLine,扫的时候维持一个当前最小值。譬如说,当我们扫到第j个位置的时候,正扫的时候我们可以有min(curLine[0], curLine[1] .. curLine[j - 1]),反扫的时候我们可以有min(curLine[j + 1], curLine[j + 2]... curLine[k - 1])。这两个值中的更小者便是prevMin[j]。 所以这个时候,我们就用O(k)的方式,得到了所有的min(f(i - 1, x))。根据这个描述,可以得到代码如下。

    public int minCostII(int[][] costs) {
        if (costs.length == 0) {
            return 0;
        }
        
        // This means in the current iteration, what is the minimum price if color j is chosen.
        int[] curHouses = new int[costs[0].length];
        
        // This data means that at color j, previous house's lowest price
        // Which is the minimum price of all houses except color j
        // It is actually equivalent to minimum price of all color at previous house if
        // the minimum price is not with color j, otherwise second minimum price of all color
        // This could lead to later optimization without using array as buffer.
        int[] prevMinCosts = new int[costs[0].length];
        // This is a helper trick parameter to build up an array (let's say minArr) of another array(let's say arr)
        // that the minArr[i] is the smallest of the array except arr[i]
        // Like minArr[i] is the smallest of (arr[0 .. i - 1] and arr[i + 1 .. n])
        int travellingMin = Integer.MAX_VALUE;
        
        for (int i = 0; i < costs.length; i++) {
            travellingMin = Integer.MAX_VALUE;
            for (int j = 0; j < costs[0].length; j++) {
                curHouses[j] = prevMinCosts[j] + costs[i][j];
                if (j > 0) {
                    travellingMin = Math.min(travellingMin, curHouses[j - 1]);
                }
                prevMinCosts[j] = travellingMin;
            }
            
            travellingMin = curHouses[costs[0].length - 1];
            for (int j = costs[i].length - 2; j >= 0; j--) {
                prevMinCosts[j] = Math.min(travellingMin, prevMinCosts[j]);
                travellingMin = Math.min(travellingMin, curHouses[j]);
            }
        }
        
        return Math.min(curHouses[0], travellingMin);
    }

跟描述上略有差异的是我们的正扫是在构建当前的curLine的过程中完成的,所以我们只需要多一步反扫就可以了。
稍微能在空间上有所突破的是我们可以少用一个prevMin数组。事实上,我们不完全需要之前一个房子的所有颜色对应的最小值,我们只需要之前一个房子对应的所有颜色中的最小花费和第二小的花费以及最小花费所对应的颜色就可以了。就是在计算当前房子的花费的时候,我们只需要看一下目前在计算的颜色和计算到上一个房子的的最小花费所对应的颜色是否一样,是的话就用当前房子当前颜色cost[i][j] + secondPrevMinCost,否则就是cost[i][j] + prevMinCost就可以了。所以我们就把维护一个数组,变成维护两个值和它所对应的颜色。如果说上一种解法用O(1)解决min(f(i - 1, x))的关键是“遍历一个数组a,然后得到另一个数组b,其中对于任意i, b[i]是数组a除去a[i]以外的最小值”,那么这一种解法就是“遍历一个数组a,得到数组里最小和第二小的值,并且知道它们的index”,这个就容易许多,就不具体阐述了,直接给出代码吧。

    public int minCostII(int[][] costs) {
        if (costs.length == 0) return 0;
        int[] curr = new int[costs[0].length];
        int[] min =  {0, Integer.MAX_VALUE};
        int[] secondMin = {0, Integer.MAX_VALUE};
        int[] nextMin = {0, Integer.MAX_VALUE};
        int[] nextSecondMin = {0, Integer.MAX_VALUE};
        for (int i = 0; i < costs.length; i++) {
            nextMin[1] = nextSecondMin[1] = Integer.MAX_VALUE;            
            for (int j = 0; j < costs[0].length; j++) {
                curr[j] = costs[i][j];
                if (i != 0) curr[j] += j == min[0] ? secondMin[1] : min[1];
                
                if (curr[j] < nextMin[1]) {
                    System.arraycopy(nextMin, 0, nextSecondMin, 0, 2);
                    nextMin[0] = j;
                    nextMin[1] = curr[j];                    
                } else if (curr[j] < nextSecondMin[1]) {
                    nextSecondMin[1] = curr[j];
                    nextSecondMin[0] = j;
                }
            }
            System.arraycopy(nextMin, 0, min, 0, 2);
            System.arraycopy(nextSecondMin, 0, secondMin, 0, 2);
        }
        return min[1];
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值