leetcode 64. 最小路径和

本文介绍了一个经典的算法问题:在一个给定的非负整数网格中找到从左上角到右下角路径上的数字总和最小的路径。文章详细探讨了使用深度优先搜索、记忆化搜索及动态规划等算法解决该问题的方法,并给出了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目叙述

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

分析

本题属于选择决策问题,因此可以使用多选择深度搜索,同时也可以使用动态求解

方法1:深度搜索(超时)

使用深度搜索来求解问题一般有两种思路:
递归的两种方案:

自顶向下” 的解决方案:首先得到最顶层的值,每递归一层计算中间结果,最终结果在递归到最下面一层函数运行完得到(比如全排列问题或者n皇后问题);

自底向上” 的解决方案:首先递归到最下面一层得到基准值,然后从最下面一层函数开始计算中间结果并逐步退出递归,最终结果在最顶层函数计算完得到。动态规划也常使用自底向上

本题采用自顶向下(这里的下方也就是基准值容易求的点)的思想。

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int res;
        int m = grid.size();
        int n = grid[0].size();
        return dfs(m-1, n-1, grid);
    }
private:
    int dfs(int i, int j, vector<vector<int>>& grid){
        if(i==0&&j==0){
            return grid[0][0];
        }
        if (i < 0 || j < 0)
            return INT_MAX;
        return grid[i][j] + min(dfs(i -1, j, grid),dfs(i,j-1,grid));
    }
};

上面的解法是超时的,一般像这种尾递归都可以使用记忆化递归

class Solution {
public:
    map<pair<int, int>, int> dic;
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        return dfs(m-1, n-1, grid);
    }
private:
    int dfs(int i, int j, vector<vector<int>>& grid){//注意此处一定要写成& grid直接写成grid仍会超时,否则应该存在一个值拷贝的耗时过程
        pair<int, int> p = make_pair(i, j);
        if(dic[p]) return dic[p];
        if(i==0&&j==0){           
            dic[p] = grid[0][0];//这句话也可以不写
            return grid[0][0];
        }
        if (i < 0 || j < 0)
            return INT_MAX;
        dic[p] = grid[i][j] + min(dfs(i -1, j, grid),dfs(i,j-1,grid));
        return dic[p];
    }
};

或者写成如下的写法:

class Solution {
public:
    //定义一个备忘录,备忘录通常是以map或者是数组充当
    vector<vector<int>> dp;
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        dp.resize(m, vector<int>(n, -1));//-1表示备忘录还没赋值
        return dfs(grid, m-1, n-1);
    }

    //定义深度搜索数组,采用自顶向下
    int dfs(vector<vector<int>>& grid, int x, int y){//注意此处一定要写成& grid直接写成grid仍会超时,否则应该存在一个值拷贝的耗时过程
        if(x<0 || y<0) return INT_MAX;
        if(x==0 && y==0){
            dp[x][y] = grid[0][0];
            return grid[0][0];
        }
        if(dp[x][y]>=0) return dp[x][y];
        //备忘录第一步,先给相应的位置进行赋值
        dp[x][y] = min(dfs(grid, x-1, y), dfs(grid, x, y-1)) + grid[x][y];
        //备忘录第二步,返回当前位置的备忘录记录
        return dp[x][y];
    }
};

方法2:动态规划

使用二维dp

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        if (grid.size() == 0 || grid[0].size() == 0) {
            return 0;
        }
        int rows = grid.size(), columns = grid[0].size();
        auto dp = vector < vector <int> > (rows, vector <int> (columns));
        dp[0][0] = grid[0][0];
        for (int i = 1; i < rows; i++) {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        for (int j = 1; j < columns; j++) {
            dp[0][j] = dp[0][j - 1] + grid[0][j];
        }
        for (int i = 1; i < rows; i++) {
            for (int j = 1; j < columns; j++) {
                dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }
        return dp[rows - 1][columns - 1];
    }
};

在这里插入图片描述

由于根据状态转移方程可知,dp[i][j] 只与当前行和上一个行的一个位置有关,而与更上面的行没有关系,那么就可以使用滚动数组进行优化空间复杂度。开辟一个长度为columns长度的一维数组作为dp数组。我们以当前的当前的还未更新的dp[i]表示 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j] , 以dp[i-1]表示 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1]

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int rows = grid.size();
        int columns = grid[0].size();
        vector<int>dp (columns, INT_MAX);
        dp[0] = 0;
        for (int i = 0; i < rows; i++) {
            dp[0] += grid[i][0];//仍旧是一行一行的处理但是对于每次处理新行,第一个位置也就是第一列都是要加上当前的行的第一个元素
            for (int j = 1; j < columns; j++) {
                dp[j] = min(dp[j], dp[j-1]) + grid[i][j];
            }
        }
        return dp[columns - 1];
    }
};

思考:求出其中的一条路径

这个求路径的思想也是基于方法1的递归思想以及记忆化的备忘录倒着求解。

class Solution {
public:
    map<pair<int, int>, int> dic;
    int minPathSum(vector<vector<int>>& grid) {
        int res;
        int m = grid.size();
        int n = grid[0].size();
        int ans = dfs(m-1, n-1, grid);
        vector<int> res_path;
        path(grid, dic, res_path, grid.size()-1, grid[0].size()-1);
        //由于这个路径是逆着求解的,所以要逆置一下
        reverse(res_path.begin(), res_path.end());
        for(auto it:res_path) cout<<it<<" ";
        return ans;
    }

private:
    int dfs(int i, int j, vector<vector<int>>& grid){
        pair<int, int> p = make_pair(i, j);
        if(dic[p]) return dic[p];
        if(i==0&&j==0){           
            dic[p] = grid[0][0];
            return grid[0][0];
        }
        if (i < 0 || j < 0)
            return INT_MAX;
        dic[p] = grid[i][j] + min(dfs(i -1, j, grid),dfs(i,j-1,grid));
        return dic[p];
    }
    //通过一个递归进行求路径
    //倒着寻找路径
    void path(vector<vector<int>>& grid, map<pair<int, int>, int>& dic, vector<int>& res_path, int i, int j){
        
        if(i<0 || j<0) return;
        if(i==0 && j==0){
            res_path.push_back(grid[0][0]);
            return;
        }
        res_path.push_back(grid[i][j]);
        if((i-1)<0){
            path(grid, dic, res_path, i, j-1);
        }else if((j-1)<0){
            pair<int, int> p1 = make_pair(i-1, j);
        }
        else{
            pair<int, int> p1 = make_pair(i-1, j);
            pair<int, int> p2 = make_pair(i, j-1);       
            if(dic[p1]<dic[p2]) path(grid, dic, res_path, i-1, j);
            else path(grid, dic, res_path, i, j-1);
        }
    }
};

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值