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

news2024/11/25 2:32:16

在这里插入图片描述

  • 博主简介:努力学习的22级计科生
  • 博主主页: @是瑶瑶子啦
  • 所属专栏: LeetCode每日一题–进击大厂

在这里插入图片描述

目录

  • 一、动态规划套路
  • 二、分析
    • 1、dp数组含义
    • 2、确定递推公式(递推函数的实现)
    • 3、dp数组初始化(base case)
    • 4、遍历顺序
  • 解题(代码):
  • 三、最终完整代码:

一、动态规划套路

动规五部曲

  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));
    }
};


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

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

在这里插入图片描述

  • Java岛冒险记【从小白到大佬之路】
  • LeetCode每日一题–进击大厂
  • 算法
  • C/C++
  • Go语言核心编程
  • 数据结构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/467186.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React面向组件编程

React面向组件编程 基本理解和使用组件三大核心属性1: state效果理解强烈注意 组件三大核心属性2: props效果理解作用编码操作 组件三大核心属性3: refs与事件处理效果理解编码2.4.4. 事件处理 收集表单数据效果理解 组件的生命周期(重要&#xff09;效果理解生命周期流程图(旧…

《庖丁解牛Linux内核分析》课后实验:实验一:反汇编分析

实验内容 参照第 1.3 节&#xff0c;将如下 C 语言代码汇编成“.s”文件&#xff0c;并分析“.s”文件中的汇编代码 的执行过程&#xff0c;其中重点关注 EBP/ESP 寄存器、EAX 寄存器、EIP 寄存器和函数调用堆栈空 间在汇编代码的执行过程是如何变化的。 int g(int x) { return…

省钱!NewBing硬核新玩法;手把手教你训练AI模特;用AI替代同事的指南;B站最易上手AI绘画教程 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『NewBing 的2种硬核新用法』阅读文档并回答问题 & AI绘图 社区同学分享了两种NewBing的新用法&#xff0c;不仅准确高效&#x…

2023年湖北省武汉安全员ABC报名时间考试通过率如何?启程别

2023年湖北省武汉安全员ABC报名时间考试通过率如何&#xff1f;启程别 都2023年了&#xff0c;建筑企业想要报考三类人员&#xff0c;不知道在哪里报名&#xff0c;具备什么样的条件才可以报考建筑安全员abc,什么时间报名考试等等一系列问题&#xff0c;启程别来给你们细细说明…

即时通讯源码自己开发好还是对接云服务好

随着科技的飞速发展&#xff0c;通信技术也日新月异。与此同时&#xff0c;即时通讯应用也越来越受欢迎。许多企业在构建应用程序时会遇到一个问题&#xff1a;使用即时通讯源码自己开发好还是对接云服务好&#xff1f;本文将探讨这两种方法的优缺点&#xff0c;并为您提供一些…

环境配置 | Win10 VSCode连接远程服务器里的docker容器

环境&#xff1a;win10, VS code, 远程服务器Ubuntu16.04&#xff08;远程服务器上已经安装好了dockers&#xff09;, 1.VScode下载 网址&#xff1a;Download Visual Studio Code - Mac, Linux, Windows 下载后双击运行 2.VSCode上配置 STEP 1.点击vscode右边工具栏点击拓展…

《嵌入式系统的WEB开发》

硬件平台&#xff1a;RK1109/T31/RK3308 软件系统&#xff1a;Linux&#xff0c;Lighttpd/ Nginx, FCGI, HTML/JS 选择使用FCGI&#xff0c;除了使用C 开发调用系统资源方便外. FCGI 可以解决CGI 多入口的问题&#xff0c;统一的入口&#xff0c;对数据同步&#xff0c;都是比…

Python 基于 Django 的学生成绩管理系统,可视化界面(附源码,教程)

1简介 对于学生成绩管理系统&#xff0c;充分运用现代化的信息技术手段&#xff0c;对于学生成绩信息管理发展的趋势就是信息化&#xff0c;信息化时代下的信息管理&#xff0c;需要深化信息管理体制与手段的改革&#xff0c;充分运用信息化手段来全方位的进行学生成绩管理系统…

VTK交互功能-callback/observe模式-vtkCommand

前言&#xff1a;本博文为系列博文&#xff0c;尝试将vtk交互功能讲清楚&#xff0c;包括常用的接口分析、常用的交互逻辑以及常用的交互Widget。若各位小伙伴有需要请多多关注&#xff0c;也希望能给各位小伙伴有所帮助。vtkCommand作为callback/obseve交互模式的基类&#xf…

0201自动配置类的导入-自动装配原理-springboot2.7.x系列

1简介 Spring Boot是基于Spring框架的&#xff0c;它的原理也是基于Spring框架的。 Spring框架是一个非常强大的框架&#xff0c;它提供了一系列的模块来帮助开发人员构建企业级的应用程序。Spring框架的核心是控制反转&#xff08;Inversion of Control&#xff0c;IoC&…

大数据技术之Spark Streaming概述

前言 数据处理延迟的长短 实时数据处理&#xff1a;毫秒级别离线数据处理&#xff1a;小时 or 天 数据处理的方式 流式&#xff08;streaming&#xff09;数据处理批量&#xff08;batch&#xff09;数据处理 spark Streaming也是基于sparkCore&#xff0c;所以底层的核心没有变…

FreeRTOS系统学习第一步:新建 FreeRTOS 工程—软件仿真

创建一个FreeRTOS系统工程 1.新建工程文件夹2.Keil新建工程2.1 New Project2.2 Select Device For Target2.3 Manage Run-Time Environment 3. 在 KEIL 工程里面新建文件组3.1在 KEIL 工程里面添加文件 4. 编写 main 函数5. 调试配置5.1 设置软件仿真5.2 修改时钟大小在时钟相关…

你是否线上有使用ThreadLocal,如果结合多线程,请慎用

随着业务的增加&#xff0c;数据量的增加&#xff0c;多线程的使用会越来越频繁&#xff0c;提升单机的处理能力。 前些日子我们线上出现了一个比较严重的故障&#xff0c;这个故障是多线程使用不当引起的&#xff0c;挺有代表性的&#xff0c;所以分享给大家&#xff0c;希望…

前端学习笔记:CSS的引入,元素选择器

这是本人学习的总结&#xff0c;主要学习资料如下 马士兵教育 目录 1、引入CSS1.1、引入CSS的方式1.2、优先级 2、元素选择器2.1、基本选择器2.1.1、选择器2.1.2、优先级 2.2、关系选择器2.2.1、优先级 2.3、属性选择器2.4、伪类选择器 1、引入CSS 1.1、引入CSS的方式 第一个…

互斥锁深度理解与使用

大家好&#xff0c;我是易安! 我们知道一个或者多个操作在CPU执行的过程中不被中断的特性&#xff0c;称为“原子性”。理解这个特性有助于你分析并发编程Bug出现的原因&#xff0c;例如利用它可以分析出long型变量在32位机器上读写可能出现的诡异Bug&#xff0c;明明已经把变量…

SpringCloud--gateway 网关

在Spring Cloud中&#xff0c;使用Gateway网关访问服务可以有多种好处&#xff0c;包括但不限于以下几点&#xff1a; 统一入口管理&#xff1a;Gateway作为统一的服务入口&#xff0c;可以对所有的请求进行统一管理和控制&#xff0c;实现微服务集中管理。 动态路由&#xff…

056:cesium 七种方法设置颜色

第056个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中设置颜色,这里用到了7种方法,查看API,还有很多种方法 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共115行)相关API参考:专栏目标示例效果 配置…

深入理解Go语言中的接口编程【17】

文章目录 接口接口接口类型为什么要使用接口接口的定义实现接口的条件接口类型变量值接收者和指针接收者实现接口的区别值接收者实现接口指针接收者实现接口下面的代码是一个比较好的面试题 类型与接口的关系一个类型实现多个接口多个类型实现同一接口接口嵌套 空接口空接口的定…

TCP教程:详解TCP连接过程

目录标题 一 、简述二 、TCP建立连接协议&#xff08;三次握手&#xff09;2.1 概述及目的2.2 第一次握手&#xff1a;客户端发送SYN报文2.3 第二次握手&#xff1a;服务器回应SYN-ACK报文2.4 第三次握手&#xff1a;客户端回应ACK报文2.5 顾客预定座位场景2.6底层原理2.7 TCP …

嵌入式之Samba服务器搭建

在嵌入式系统开发应用平台中&#xff0c;tftp、nfs和samba服务器是最常用的文件传输工具 tftp和nfs是在嵌入式Linux开发环境中经常使用的传输工具 samba则是Linux和Windows之间的文件传输工具。 下面演示在linux上搭建Samba服务器 sudo apt-get install samba chmod -R 77…