【Leetcode每日一刷】动态规划:931. 下降路径最小和

勤奋不是嘴上说说而已,而是实际的行动,在勤奋的苦度中持之以恒,永不退却。业精于勤,荒于嬉;行成于思,毁于随。在人生的仕途上,我们毫不迟疑地选择勤奋,她是几乎于世界上一切成就的催产婆。只要我们拥着勤奋去思考,拥着勤奋的手去耕耘,用抱勤奋的心去对待工作,浪迹红尘而坚韧不拔,那么,我们的生命就会绽放火花,让人生的时光更加的闪亮而精彩。

导读:本篇文章讲解 【Leetcode每日一刷】动态规划:931. 下降路径最小和,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

在这里插入图片描述

在这里插入图片描述

一、动态规划套路

动规五部曲

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式(状态转移方程)
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

二、分析

就是说你可以站在 matrix 的第一行的任意一个元素,需要下降到最后一行。

每次下降,可以向下、向左下、向右下三个方向移动一格。也就是说,可以从 matrix[i][j] 降到 matrix[i+1][j]matrix[i+1][j-1]matrix[i+1][j+1] 三个位置。

1、dp数组含义

从第一行(matrix[0][…])向下落,落到位置 matrix[i][j] 的最小路径和为 dp(matrix, i, j)。

int dp(vector<vector<int>>& matrix, int i, int j);

写出主体逻辑代码:

int minFallingPathSum(vector<vector<int>>& matrix) {
    int n = matrix.size();
    int res = INT_MAX;

    // 终点可能在最后一行的任意一列
    for (int j = 0; j < n; j++) {
        res = min(res, dp(matrix, n - 1, j));
    }

    return res;
}

2、确定递推公式(递推函数的实现)

对于 matrix[i][j],只有可能从 matrix[i-1][j], matrix[i-1][j-1], matrix[i-1][j+1] 这三个位置转移过来

那么,只要知道到达 (i-1, j), (i-1, j-1), (i-1, j+1) 这三个位置的最小路径和,加上 matrix[i][j] 的值,就能够计算出来到达位置 (i, j) 的最小路径和:

int dp(vector<vector<int>>& matrix, int i, int j) {
    // 非法索引检查
    if (i < 0 || j < 0 ||
        i >= matrix.size() ||
        j >= matrix[0].size()) {
        // 返回一个特殊值
        return 99999;
    }
    // base case
    if (i == 0) {
        return matrix[i][j];
    }
    // 状态转移
    return matrix[i][j] + min({
            dp(matrix, i - 1, j), 
            dp(matrix, i - 1, j - 1),
            dp(matrix, i - 1, j + 1)
        });
}

int min(int a, int b, int c) {
    return min(a, min(b, c));
}

3、dp数组初始化(base case)

	int dp(vector <vector <int>> & matrix, vector<vector<int>>& memo, int i, int j){
		//base case 
		if(i == 0){
			 return matrix[0][j];
        }
	}

4、遍历顺序

初始化初始化第一行,那么根据二维数组,以及确定的data base的位置和最终状态,确定:从上往下遍历

   // 进行状态转移
        memo[i][j] = matrix[i][j] + min(
                dp(matrix, memo, i - 1, j), 
                dp(matrix, memo, i - 1, j - 1),
                dp(matrix, memo, i - 1, j + 1)
            );

解题(代码):


int dp(vector<vector<int>>& matrix, int i, int j) {
    // 非法索引检查
    if (i < 0 || j < 0 ||
        i >= matrix.size() ||
        j >= matrix[0].size()) {
        // 返回一个特殊值
        return 99999;
    }
    // base case
    if (i == 0) {
        return matrix[i][j];
    }
    // 状态转移
    return matrix[i][j] + min({
            dp(matrix, i - 1, j), 
            dp(matrix, i - 1, j - 1),
            dp(matrix, i - 1, j + 1)
        });
}

int min(int a, int b, int c) {
    return min(a, min(b, c));
}

上面代码能够暴力递归解决,但是有很多***重叠子问题***,降低了时间效率。

可以采用备忘录,记录下来已经确定的状态,不用每次去递归算。来消除重叠子问题:


class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& matrix) {
        int n = matrix.size();
        int res = INT_MAX;
        // 备忘录里的值初始化为 66666
        vector<vector<int>> memo(n, vector<int>(n, 66666));
        // 终点可能在 matrix[n-1] 的任意一列
        for (int j = 0; j < n; j++) {
            res = min(res, dp(matrix, memo, n - 1, j));
        }
        return res;
    }

    int dp(vector<vector<int>>& matrix, vector<vector<int>>& memo, int i, int j) {
        // 1、索引合法性检查
        if (i < 0 || j < 0 ||
            i >= matrix.size() ||
            j >= matrix[0].size()) {
            
            return 99999;
        }
        // 2、base case
        if (i == 0) {
            return matrix[0][j];
        }
        // 3、查找备忘录,防止重复计算
        if (memo[i][j] != 66666) {
            return memo[i][j];
        }
        // 进行状态转移
        memo[i][j] = matrix[i][j] + min(
                dp(matrix, memo, i - 1, j), 
                dp(matrix, memo, i - 1, j - 1),
                dp(matrix, memo, i - 1, j + 1)
            );
        return memo[i][j];
    }

    int min(int a, int b, int c) {
        return min(a, min(b, c));
    }
};
  • memo的初始值为什么是66666:

备忘录 memo 数组的作用是什么?

就是防止重复计算,将 dp(matrix, i, j) 的计算结果存进 memo[i][j],遇到重复计算可以直接返回。

那么,我们必须要知道 memo[i][j] 到底存储计算结果没有,对吧?如果存结果了,就直接返回;没存,就去递归计算。

所以,memo 的初始值一定得是特殊值,和合法的答案有所区分。

我们回过头看看题目给出的数据范围:

matrix 是 n x n 的二维数组,其中 1 <= n <= 100;对于二维数组中的元素,有 -100 <= matrix[i][j] <= 100。

假设 matrix 的大小是 100 x 100,所有元素都是 100,那么从第一行往下落,得到的路径和就是 100 x 100 = 10000,也就是最大的合法答案。

类似的,依然假设 matrix 的大小是 100 x 100,所有元素是 -100,那么从第一行往下落,就得到了最小的合法答案 -100 x 100 = -10000。

也就是说,这个问题的合法结果会落在区间 [-10000, 10000] 中。

所以,我们 memo 的初始值就要避开区间 [-10000, 10000],换句话说,memo 的初始值只要在区间 (-inf, -10001] U [10001, +inf) 中就可以。

  • 索引合法性检测:99999:
    • 对于不合法的索引,返回值如何确定,要看状态转移方程的逻辑
    • 状态转移方程如下:
int dp(vector <vector<int>> & matrix, int i, int j) {
	return matrix[i][j] + min(
		dp(matrix, memo, i-1,j),
		dp(matrix, memo, i-1,j-1)
		dp(matrix, memo, i-1,j+1)
	);
}

通过状态方程可知,如果索引不合法,是取该状态+前三个状态的最小值。在i-1j+1中,均有可能出现不合法的情况。

只需让其返回一个特殊的大值,让min取不到它即可。

通过上面分析,该题的最终状态结构范围在[-10000, 10000] 中。所以只需返回>10000的值即可。更准确来说的只要返回区间 [10001, +inf) 中的一个值,就能保证不会被取到

三、最终完整代码:

class Solution {
public:
    int minFallingPathSum(vector<vector<int>>& matrix) {

        int n = matrix.size();
        //防止重复计算,创建一个备忘录数组
        vector < vector<int> > memo(n, vector<int>(n,55555));//初始化备忘录数组

        //主体逻辑代码:(遍历最后一行,终点可能在最后一行的任何一列)

        int ans = INT_MAX;
        for (int i = 0; i < matrix.size(); i++){
           ans = min(ans,dp(matrix,memo,n-1,i));
        }
        return ans;
    }

    int dp(vector<vector<int>> &matrix, vector< vector<int> > &memo, int i,int j){
        //1)首先要检测下标是否合法
        if(i<0||j<0||i>=matrix.size()||j>=matrix.size()){
            return 55555;
        }
        //2)初始化dp数组
        if(i==0){
            return matrix[i][j];
        }

        //备忘录查找防止重复
        if(memo[i][j]!=55555){
            return memo[i][j];
        }

        //3)状态转移方程
        //不要忘记装进备忘录里哦
        memo[i][j] = matrix[i][j] + min_(
            dp(matrix,memo,i-1,j),
            dp(matrix,memo,i-1,j-1),
            dp(matrix,memo,i-1,j+1)
        );
        return memo[i][j];
    }
    int min_(int a, int b, int c){
        return min (c,min(a,b));
    }
};


欢迎在评论区交流和留下你的想法和建议

如果对你有用,还请:💭评论+👍🏻点赞+⭐收藏+➕关注

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/142426.html

(0)

相关推荐

发表回复

登录后才能评论