zoukankan      html  css  js  c++  java
  • [LeetCode 1368] Minimum Cost to Make at Least One Valid Path in a Grid

    Given a m x n grid. Each cell of the grid has a sign pointing to the next cell you should visit if you are currently in this cell. The sign of grid[i][j] can be:

    • 1 which means go to the cell to the right. (i.e go from grid[i][j] to grid[i][j + 1])
    • 2 which means go to the cell to the left. (i.e go from grid[i][j] to grid[i][j - 1])
    • 3 which means go to the lower cell. (i.e go from grid[i][j] to grid[i + 1][j])
    • 4 which means go to the upper cell. (i.e go from grid[i][j] to grid[i - 1][j])

    Notice that there could be some invalid signs on the cells of the grid which points outside the grid.

    You will initially start at the upper left cell (0,0). A valid path in the grid is a path which starts from the upper left cell (0,0) and ends at the bottom-right cell (m - 1, n - 1) following the signs on the grid. The valid path doesn't have to be the shortest.

    You can modify the sign on a cell with cost = 1. You can modify the sign on a cell one time only.

    Return the minimum cost to make the grid have at least one valid path.

     

    Example 1:

    Input: grid = [[1,1,1,1],[2,2,2,2],[1,1,1,1],[2,2,2,2]]
    Output: 3
    Explanation: You will start at point (0, 0).
    The path to (3, 3) is as follows. (0, 0) --> (0, 1) --> (0, 2) --> (0, 3) change the arrow to down with cost = 1 --> (1, 3) --> (1, 2) --> (1, 1) --> (1, 0) change the arrow to down with cost = 1 --> (2, 0) --> (2, 1) --> (2, 2) --> (2, 3) change the arrow to down with cost = 1 --> (3, 3)
    The total cost = 3.
    

    Example 2:

    Input: grid = [[1,1,3],[3,2,2],[1,1,4]]
    Output: 0
    Explanation: You can follow the path from (0, 0) to (2, 2).
    

    Example 3:

    Input: grid = [[1,2],[4,3]]
    Output: 1
    

    Example 4:

    Input: grid = [[2,2,2],[2,2,2]]
    Output: 3
    

    Example 5:

    Input: grid = [[4]]
    Output: 0
    

     

    Constraints:

    • m == grid.length
    • n == grid[i].length
    • 1 <= m, n <= 100

    Bottom up DP does not work as a cell can visit all its 4 neighbors. Initially I came up with a standard BFS then update each cell's min cost during traversal. Each cell is only visited once.  The problem using a Queue in BFS is that the traversal order is decided by the insertion order of a cell and has nothing to do with the cost to reach a cell. This is incorrect, a counter example in example 1 is that we'll visit cell(1, 0) with a cost of 1 when instead there is a 0 cost path to reach (1, 0). 

    Solution 1. PriorityQueue

    To fix the above incorrect traversal order, we use PriorityQueue and track each cell's cost to reach from (0, 0). If the current visiting cell has a bigger cost than what have been reported so far as min cost, skip this cell as we know there is a better path with smaller cost to reach this cell. (We must not skip equal cost cells as we don't know which one will lead to an optimal answer so we need to check all of them)

    The runtime is similar with Dijkstra's runtime, O(M * N + M * N * log (M * N))

    class Solution {
        public int minCost(int[][] grid) {
            int m = grid.length;
            int n = grid[0].length;
            int[][] cost = new int[m][n];
            for(int i = 0; i < m; i++) {
                Arrays.fill(cost[i], (int)1e7);
            }
            PriorityQueue<int[]> q = new PriorityQueue<>(Comparator.comparingInt(a -> a[2]));
            q.add(new int[]{0,0,0});
            cost[0][0] = 0;
            
            int[] dx = {0,0,1,-1};
            int[] dy = {1,-1,0,0};
        
            while(q.size() > 0) {
                int[] curr = q.poll();
                if(curr[2] > cost[curr[0]][curr[1]]) {
                    continue;
                }
                if(curr[0] == m - 1 && curr[1] == n - 1) {
                    break;
                }
                int k = grid[curr[0]][curr[1]];
                for(int i = 0; i < 4; i++) {
                    int x = curr[0] + dx[i];
                    int y = curr[1] + dy[i];
                    int c = cost[curr[0]][curr[1]] + (i == k - 1 ? 0 : 1);
                    if(x >= 0 && x < m && y >= 0 && y < n && c < cost[x][y]) {
                        cost[x][y] = c;
                        q.add(new int[]{x,y,c});
                    }
                }
            }
            return cost[m - 1][n - 1];
        }
    }

    Solution 2. Deque

    The reason that we use PriorityQueue in solution 1 is to ensure the correct traversal order based on cost from starting point. We can also use a Deque and do the following to achieve the same.

    1. If visiting its neighbor's cost is 0, i.e, the current cell already has the direction to visit this neighbor, we add this neighbor along with cost to the front of deque.

    2. Otherwise, we add this neighbor along with cost (+1  in this case) to the back of deque.

    Polling from the front of deque ensures that we keep visiting cells with no added costs until otherwise. Once a cell is visited, we mark it and won't visit it again.

    The runtime is O(M * N).

    class Solution {
        public int minCost(int[][] grid) {
            int m = grid.length;
            int n = grid[0].length;
            boolean[][] visited = new boolean[m][n];
            Deque<int[]> dq = new ArrayDeque<>();
            dq.add(new int[]{0,0,0});
            
            int[] dx = {0,0,1,-1};
            int[] dy = {1,-1,0,0};
        
            while(dq.size() > 0) {
                int[] curr = dq.pollFirst();
                if(visited[curr[0]][curr[1]]) {
                    continue;
                }
                visited[curr[0]][curr[1]] = true;
                if(curr[0] == m - 1 && curr[1] == n - 1) {
                    return curr[2];
                }
                for(int i = 0; i < 4; i++) {
                    int x = curr[0] + dx[i];
                    int y = curr[1] + dy[i];
                    if(x >= 0 && x < m && y >= 0 && y < n && !visited[x][y]) {
                        if(i + 1 == grid[curr[0]][curr[1]]) {
                            dq.addFirst(new int[]{x, y, curr[2]});
                        }
                        else {
                            dq.addLast(new int[]{x, y, curr[2] + 1});
                        }
                    }
                }
            }
            return -1;
        }
    }

    Solution 3. DFS + BFS

    The gist of solution 2 is essentially using a deque to ensure we exhaust all reachable cells without adding any cost, then try to change cell directions with added cost to explore unvisited cells. This can also be done by the following DFS then BFS algorithm.

    1. From the start point, do a dfs to visit all reachable cells with cost 0, update these cells' cost and add them into a Queue.

    2. As long as the queue is not empty, repeat the following.

    2a. increment the current cost by 1, representing we've exhausted all reachable cells and need to allow 1 more direction change to keep exploring.

    2b. for all the cell in queue, do no-added-cost dfs on their neighbors if neighbors haven't been visited. Update these cells' cost and add all reached cells to queue. These newly added cells will be the starting points from the next bfs iteration. 

    The above algorithm is a controlled version of the standard BFS. At each BFS iteration, we update the current allowed cost and use DFS to add all unvisited cells that have 0 cost to reach from the cells in the current BFS iteration. 

    The runtime is O(M * N).

    class Solution {
        private int[] dx = {0,0,1,-1};
        private int[] dy = {1,-1,0,0};
        private int m;
        private int n;
        private int[][] cost;
        private Queue<int[]> q;
        public int minCost(int[][] grid) {
            int currCost = 0;
            m = grid.length;
            n = grid[0].length;
            cost = new int[m][n];
            for(int i = 0; i < m; i++) {
                Arrays.fill(cost[i], Integer.MAX_VALUE);
            }
            q = new LinkedList<>();
            dfs(grid, 0, 0, currCost);
            
            while(!q.isEmpty()) {
                currCost++;
                int sz = q.size();
                for(int i = 0; i < sz; i++) {
                    int[] curr = q.poll();
                    for(int j = 0; j < 4; j++) {
                        dfs(grid, curr[0] + dx[j], curr[1] + dy[j], currCost);
                    }
                }
            }
            return cost[m - 1][n - 1];
        }
        private void dfs(int[][] grid, int x, int y, int currCost) {
            if(x < 0 || x >= m || y < 0 || y >= n || cost[x][y] < Integer.MAX_VALUE) {
                return;
            }
            cost[x][y] = currCost;
            q.add(new int[]{x, y});
            dfs(grid, x + dx[grid[x][y] - 1], y + dy[grid[x][y] - 1], currCost);
        }
    }
  • 相关阅读:
    Webkit是如何加载网页的
    代码无错是优? 工厂模式
    JavaScript 记忆 Memoization
    输入一个新的网址后到完全显示页面,浏览器做了哪些工作?
    向服务器请求数据的五种技术
    setTimeout(0) 即将退役
    JS 对象机制深剖——new 运算符
    正则表达式中的回溯
    闭包与柯里化
    正则表达式工作原理
  • 原文地址:https://www.cnblogs.com/lz87/p/12393706.html
Copyright © 2011-2022 走看看