LeetCode-64. 最小路徑和


題目描述

給定一個包含非負整數的 m x n 網格,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和為最小。

說明:每次只能向下或者向右移動一步。

示例:

輸入:
[[1,3,1],
[1,5,1],
[4,2,1]
]
輸出: 7
解釋: 因為路徑 1→3→1→1→1 的總和最小。

思路

以輸入為 3*3 的網格為例,其中 m=3,n=3
[1,3,1]
[1,5,1]
[4,2,1]
由於每次只能向下或者向右移動,則坐標(0,0)的最小路徑就等於當前節點加上(0,1)或(1,0)的最小值,這樣可以得到遞歸方程:
dp(0,0) = grid(0,0) + min(dp(1,0),dp(0,1))
當到達網格的邊界時,下一步只能往右走或者往下走。這樣可以得到這個問題的一般遞歸解法。

一般遞歸解法

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        return minPathSumRecusion(grid, 0, 0);
    }
private:
    int minPathSumRecusion(vector<vector<int>>& grid, int m, int n){
        if(grid.size() == 0)
            return 0;
        
        if(m == grid.size()-1 && n == grid[0].size()-1)
            return grid[m][n];
        
        if(m == grid.size()-1)
            return grid[m][n]+minPathSumRecusion(grid,m,n+1);
        
        if(n == grid[0].size()-1)
            return grid[m][n]+minPathSumRecusion(grid,m+1,n);

        return grid[m][n]+ min(minPathSumRecusion(grid,m+1,n), minPathSumRecusion(grid,m,n+1));
    }
};

在leetcode上提交之后,不出意外的又超時了。下面先進行記憶化搜索進行優化,再進行動態規划算法的改寫。

記憶化搜索遞歸

在得到一般遞歸解法后進行記憶化搜索的步驟一般比較固定,先建立每一步的索引表,然后初始化邊界情況,然后在遞歸函數中判斷索引表中的數據是否存在,如果存在則直接取出作為本次遞歸的值,不存在則繼續進行下一步遞歸,遞歸返回時填寫索引表中的相應值。
這個問題比較特殊的地方在於邊界情況有稍許復雜:

  • grid為0的情況
  • 下一步只能往右的情況
  • 下一步只能往下的情況
class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        if(grid.size() == 0)
            return 0;
        int m = grid.size();
        int n = grid[0].size();
        m_memo = vector<vector<int>>(m+1,vector<int>(n+1,0));
        //初始化邊界情況
        for(int i = n-1; i >= 0;--i)
            m_memo[m-1][i] = grid[m-1][i] + m_memo[m-1][i+1];
        for(int j = m-1; j >= 0;--j)
            m_memo[j][n-1] = grid[j][n-1] + m_memo[j+1][n-1];

        return minPathSumRecusion(grid, 0, 0);
    }
private:
    int minPathSumRecusion(vector<vector<int>>& grid, int m, int n){

        if(grid.size() == 0)
            return 0;
        
        if(m_memo[m][n] != 0)
        {
            return m_memo[m][n];
        }    

        if(m == grid.size()-1 && n == grid[0].size()-1)
        {
            m_memo[m][n] = grid[m][n];
            return m_memo[m][n];
        }
        
        if(m == grid.size()-1)
        {
            return m_memo[m][n];
        }
        
        if(n == grid[0].size()-1)
        {
            return m_memo[m][n];
        }
            
        m_memo[m][n] = min(grid[m][n]+minPathSumRecusion(grid,m+1,n), grid[m][n]+minPathSumRecusion(grid,m,n+1));
        return m_memo[m][n];
    }
    vector<vector<int>> m_memo;
};

動態規划

按照上面的遞歸公式和記憶化搜索算法,如果把遞歸算法從(0,0)逐步遞歸得到(m,n)看作是自頂向下,那么從(m,n)開始通過循環逐步得到(0,0)就可以看作是自底向上,我們可以進一步得出此題的動態規划解法。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        if(grid.size() == 0)
            return 0;
        int m = grid.size();
        int n = grid[0].size();
        m_memo = vector<vector<int>>(m+1,vector<int>(n+1,0));
        //初始化邊界情況
        for(int i = n-1; i >= 0;--i)
            m_memo[m-1][i] = grid[m-1][i] + m_memo[m-1][i+1];
        for(int j = m-1; j >= 0;--j)
            m_memo[j][n-1] = grid[j][n-1] + m_memo[j+1][n-1];

        for(int i = m-2; i >= 0;--i)
        {
            for(int j = n-2; j >= 0;--j)
            {
                m_memo[i][j] = grid[i][j] + min(m_memo[i][j+1], m_memo[i+1][j]);
            }
        }
        return m_memo[0][0];
    }
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM