leetcode hard模式专杀 1368. Minimum Cost to Make at Least One Valid Path in a Grid

题干: https://leetcode.com/problems/minimum-cost-to-make-at-least-one-valid-path-in-a-grid/

 

又是这类grid的题目,虽然心里知道大概用类似动态规划的思路去做,但是做着就发现一个问题,stack overflow了,究其原因,是这题的直接子问题非常难以划分,例如我一开始尝试从某坐标[i,j]找其cost值时,我问自己,这个cost值是否跟该坐标的邻居处的cost值有相关性?看起来很直接啊,当然有,如果邻居的cost值都知道了,再根据本处的坐标指向(代表上下左右),就可以确定本处的最小cost值了,这看起来非常合理啊。可是再一细想就有问题,就是任意一处坐标其邻居最大有4个,最小有两个,并且这些邻居坐标并不必然保证问题规模递减,例如求[0,1]的邻居有[1,1],但同时也有[0,0]啊,而[0,0]坐标处的问题规模不比[0,1]小啊。

这样我们就没法像求fibonacci数列那样,用类似这种弄个dp[n]=dp[n-1]+dp[n-2]来算了,因为fibonacci的这个递推公式有着明显的规模递减的方向性,而本题没有,如果强行用这种递推公式,就会产生stack overflow,因为大小规模问题互相循环依赖嘛。

苦思冥想依然无果,后来索性在纸上画图找灵感,发现一个点,我们要找【0,0】处的cost,正着推比较难,会有上面的stackoverflow问题,那么反着来呢?例如我已经知道最右下角的点[m-1,n-1], 到自己的path的cost是0,那么我们就可以很容易地顺着指向该位置的箭头找出所有cost为0的坐标,找出了所有cost为0的坐标后,下一步,就可以通过找所有cost为0的位置的直接邻居,如果这些邻居的cost还未定的话,我们就可以确定这些邻居处的cost一定为1,因为最多只要改一下箭头反向指向自己为0的邻居,就可以顺着链条达到【m-1,n-1】点了,理解这一点光分析数字是不行的,需要画图,看几何意义,其实也不难理解,只要想象说箭头像流水一样,是否有一条连续的流水指向最终的[m-1,n-1]点即可。

找到了这些cost为1的邻居后并不代表着所有cost为1的点都找到了,还需要找出流水指向任意这些点的上游点,这样就找出来了所有cost为1的点。然后依次类推,可以找出所有cost为2的点,3的点等等,可以确定这样做下去,一定能找到某个cost为level的点集合是包含[0][0]处的点的,这样就可以停止计算了。

 

基于以上方法,只要写两个核心函数即可:

1,基于cost为k的点标记其所有未被标记的邻居的cost值为k+1

2, 找某点的上游流水中的所有点,并标记为跟当前点相同的cost(可以形象地描述为向上游染色)

 

代码如下:

package com.example.demo.leetcode;


/**
 * leetcode 1368
 */
public class MinimumCostValidPath {
    /**
     * left upper grid[0][0]
     * right bottom grid[m-1][n-1]
     * direction: 1: right , 2: left, 3: down, 4: up, might start from boarder so could go out of board
     * @param grid
     * @return
     */
    public int minCost(int[][] grid) {
        int rowCnt = grid.length;
        int colCnt = grid[0].length;

        int[][] costtable = new int[rowCnt][colCnt];
        for(int i = 0;i<rowCnt; i++){
            for(int j=0;j<colCnt;j++){
                costtable[i][j]=-1;
            }
        }

        costtable[rowCnt-1][colCnt-1] = 0;

        dye(costtable, grid);

        for(int i=0;i<rowCnt;i++){
            for(int j=0;j<colCnt;j++){
                System.out.print(costtable[i][j]+" ");
            }
            System.out.println("");
        }

        System.out.println("======");

        return costtable[0][0];

    };

    private void dyeDirectNeighbor(int[][] grid, int r, int c, int[][] costtable, int level){
        int rowCnt = grid.length;
        int colCnt = grid[0].length;
        //上,如果存在,且costtable未设值,则设值其为level
        if(r>0 && costtable[r-1][c]==-1){
            costtable[r-1][c]=level;
        }
        //下
        if(r<rowCnt-1 && costtable[r+1][c]==-1){
            costtable[r+1][c]=level;
        }
        //左
        if(c>0 && costtable[r][c-1]==-1){
            costtable[r][c-1]=level;
        }
        //右
        if(c<colCnt-1 && costtable[r][c+1]==-1){
            costtable[r][c+1]=level;
        }
    }

    /**
     * find all those costtable elements that equals level, do dyechain for all of them
     * @param grid
     * @param costtable
     * @param level
     */
    private void bulkDyeChain(int[][] grid, int[][] costtable, int level){
        int rowCnt = grid.length;
        int colCnt = grid[0].length;
        for(int i=0;i<rowCnt;i++){
            for(int j = 0;j<colCnt;j++){
                if(costtable[i][j]==level){
                    dyeChain(i,j,costtable, grid);
                }
            }
        }
    }

    //找出所有cost=level的grid, 进行染色(只是个比喻,其实就是设置costtable相应坐标处的值
    //如果这个过程中,计算值已经包含了[0][0]坐标,则返回true,说明计算结束了
    //重要前提: 计算level=k时需要确保level=k-1的值已经计算过
    private boolean innerDye(int[][] costtable, int[][] grid, int level){
        int rowCnt = grid.length;
        int colCnt = grid[0].length;
        if(level==0){
            dyeChain(rowCnt-1, colCnt-1, costtable, grid);
        }else{
            // 找出所有costtable值为level-1的坐标位置,染色其未被设值的costtable直接邻居坐标为level
            for(int i=0;i<rowCnt;i++){
                for(int j=0;j<colCnt;j++){
                    if(costtable[i][j]==level-1){
                        dyeDirectNeighbor(grid, i, j, costtable, level);
                    }
                }
            }
            bulkDyeChain(grid, costtable, level);
        }

        if(costtable[0][0]==-1){
            return false;
        }else{
            return true;
        }
    }

    /**
     */
    public void dye(int[][] costtable, int[][] grid){
        int dyeLevel = 0;
        boolean contains00 = false;
        while(true){
            contains00 = innerDye(costtable, grid, dyeLevel);
            dyeLevel++;
            if(contains00){
                break;
            }
        }
    }


    // assume know costtable[r][c], for those pointing to [r][c], set the same value, recursively, only hande those point to me
    public void dyeChain(int r, int c, int[][] costtable, int[][] grid){
        int rowCnt = grid.length;
        int colCnt = grid[0].length;

        // check upper direction pointing downwards and not set
        if(r>0 && grid[r-1][c]==3){
            if(costtable[r-1][c]==-1){
                costtable[r-1][c] = costtable[r][c];
                dyeChain(r-1,c, costtable, grid);
            }
        }
        // check bottom direction pointing upwards and not set
        if(r<rowCnt-1 && grid[r+1][c]==4){
            if(costtable[r+1][c]==-1){
                costtable[r+1][c] = costtable[r][c];
                dyeChain(r+1,c, costtable, grid);
            }
        }
        // check left direction point right and not set
        if(c>0 && grid[r][c-1]==1){
            if(costtable[r][c-1]==-1){
                costtable[r][c-1] = costtable[r][c];
                dyeChain(r,c-1, costtable, grid);
            }
        }
        // check right direction point left and not set
        if(c<colCnt-1 && grid[r][c+1]==2){
            if(costtable[r][c+1]==-1){
                costtable[r][c+1] = costtable[r][c];
                dyeChain(r,c+1, costtable, grid);
            }
        }
    }


    // todo think about edge case
    public static void main(String[] args) {
        MinimumCostValidPath demo = new MinimumCostValidPath();
        int[][] grid = {{1,1,1,1},{2,2,2,2},{1,1,1,1},{2,2,2,2}};
        int[][] grid2 = {{1,1,3},{3,2,2},{1,1,4}};
        int[][] grid3 = {{1,2},{4,3}};
        int[][] grid4 = {
                {3,4,3},
                {2,2,2},
                {2,1,1},
                {4,3,2},
                {2,1,4},
                {2,4,1},
                {3,3,3},
                {1,4,2},
                {2,2,1},
                {2,1,1},
                {3,3,1},
                {4,1,4},
                {2,1,4},
                {3,2,2},
                {3,3,1},
                {4,4,1},
                {1,2,2},
                {1,1,1},
                {1,3,4},
                {1,2,1},
                {2,2,4},
                {2,1,3},
                {1,2,1},
                {4,3,2},
                {3,3,4},
                {2,2,1},
                {3,4,3},
                {4,2,3},
                {4,4,4}};
        int ret = demo.minCost(grid);
        int ret2 = demo.minCost(grid2);
        int ret3 = demo.minCost(grid3);
        int ret4 = demo.minCost(grid4);

        System.out.println("Min cost 1: "+ret);
        System.out.println("Min cost 2: "+ret2);
        System.out.println("Min cost 3: "+ret3);
        System.out.println("Min cost 4: "+ret4);
    }
}

 

 

反思:

碰到容易产生循环依赖,stackoverflow的题目,就尝试从一端开始想起,想办法通过约束使得事情的进展朝着一个方向前进,而不是四处杂乱无章的方向

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值