算法沉淀——动态规划之路径问题(leetcode真题剖析)

在这里插入图片描述

算法沉淀——动态规划之路径问题

  • 01.不同路径
  • 02.不同路径 II
  • 03.珠宝的最高价值
  • 04.下降路径最小和
  • 05.最小路径和
  • 06.地下城游戏

01.不同路径

题目链接:https://leetcode.cn/problems/unique-paths/

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例 1:

输入:m = 3, n = 7
输出:28

示例 2:

输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下

示例 3:

输入:m = 7, n = 3
输出:28

示例 4:

输入:m = 3, n = 3
输出:6

提示:

  • 1 <= m, n <= 100
  • 题目数据保证答案小于等于 2 * 109

思路

这是一个典型的动态规划问题。以下是解题的一般步骤:

  1. 状态表示: 对于路径类问题,有两种状态表示方式,选择其中之一。这里选择从起始位置出发,到达 [i, j] 位置的方式:

    dp[i][j] 表示从起始位置到达 [i, j] 位置的路径数。

  2. 状态转移方程: 分析从 [i, j] 位置出发的一小步,有两种情况:

    • [i-1, j] 位置向下走一步,转移到 [i, j] 位置;
    • [i, j-1] 位置向右走一步,转移到 [i, j] 位置。

    因此,状态转移方程为:dp[i][j] = dp[i-1][j] + dp[i][j-1]

  3. 初始化:dp 数组前添加一行和一列,初始化 dp[0][1] 位置为 1

  4. 填表顺序: 从上往下,每一行从左往右填写。

  5. 返回值: 返回 dp[m][n] 的值,表示从起始位置到达终点位置的路径数。

代码

class Solution {
public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m+1,vector<int>(n+1,0));dp[1][1]=1;for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){if(i==1&&j==1) continue;dp[i][j]=dp[i-1][j]+dp[i][j-1];}}return dp[m][n];}
};

02.不同路径 II

题目链接:https://leetcode.cn/problems/unique-paths-ii/

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 10 来表示。

示例 1:

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

示例 2:

输入:obstacleGrid = [[0,1],[0,0]]
输出:1 

提示:

  • m == obstacleGrid.length
  • n == obstacleGrid[i].length
  • 1 <= m, n <= 100
  • obstacleGrid[i][j]01

思路

根据上题分析,这题如果某个位置 [i - 1, j] 或者 [i, j - 1] 上存在障碍物,说明从这两个位置到达 [i, j] 的路径是被阻挡的,因此在计算 dp[i][j](表示从起点到达 [i, j] 的路径数)时,可以直接将 dp[i][j] 设为零,其余同上题。

代码

class Solution {
public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int m=obstacleGrid.size(),n=obstacleGrid[0].size();vector<vector<int>> dp(m+1,vector<int>(n+1,0));dp[1][0]=1;for(int i=1;i<=m;++i)for(int j=1;j<=n;++j)if(obstacleGrid[i-1][j-1]==0)dp[i][j]=dp[i-1][j]+dp[i][j-1];return dp[m][n];}
};

03.珠宝的最高价值

题目链接:https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/

现有一个记作二维矩阵 frame 的珠宝架,其中 frame[i][j] 为该位置珠宝的价值。拿取珠宝的规则为:

  • 只能从架子的左上角开始拿珠宝
  • 每次可以移动到右侧或下侧的相邻位置
  • 到达珠宝架子的右下角时,停止拿取

注意:珠宝的价值都是大于 0 的。除非这个架子上没有任何珠宝,比如 frame = [[0]]

示例 1:

输入: frame = [[1,3,1],[1,5,1],[4,2,1]]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最高价值的珠宝

提示:

  • 0 < frame.length <= 200
  • 0 < frame[0].length <= 200

思路

在处理这类问题时,动态规划的状态表可以采用两种主要形式:一是从某个位置出发,描述到达其他位置的情况;二是从起始位置到达某个位置,描述达到该位置时的状态。在这里,我们选择第二种方式定义状态表:

我们使用 dp[i][j] 表示从起始位置到达 [i, j] 位置时的最大价值。在考虑到达 [i, j] 的两种方式时,即从上方 [i - 1, j] 或从左侧 [i, j - 1] 到达,我们需要选择其中最大价值的路径。因此,状态转移方程为:

dp[i][j]=max(dp[i-1][j],dp[i][j-1])+frame[i-1][j-1];

在初始化过程中,可以添加一个辅助结点,并将所有值初始化为零。填表的顺序是从上往下逐行填写,每一行从左往右。最后,我们应该返回 dp[m][n] 的值,表示在整个网格中的最大价值。

代码

class Solution {
public:int jewelleryValue(vector<vector<int>>& frame) {int m=frame.size(),n=frame[0].size();vector<vector<int>> dp(m+1,vector<int>(n+1,0));for(int i=1;i<=m;++i)for(int j=1;j<=n;++j)dp[i][j]=max(dp[i-1][j],dp[i][j-1])+frame[i-1][j-1];return dp[m][n];}
};

04.下降路径最小和

题目链接:https://leetcode.cn/problems/minimum-falling-path-sum/

给你一个 n x n方形 整数数组 matrix ,请你找出并返回通过 matrix下降路径最小和

下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)(row + 1, col) 或者 (row + 1, col + 1)

示例 1:

输入:matrix = [[2,1,3],[6,5,4],[7,8,9]]
输出:13
解释:如图所示,为和最小的两条下降路径

示例 2:

输入:matrix = [[-19,57],[-40,-5]]
输出:-59
解释:如图所示,为和最小的下降路径

提示:

  • n == matrix.length == matrix[i].length
  • 1 <= n <= 100
  • -100 <= matrix[i][j] <= 100

在处理这种「路径类」的问题时,动态规划的状态表一般有两种常见形式:一是从某个位置出发,描述到达其他位置的情况;二是从起始位置到达某个位置,描述达到该位置时的状态。在这里,我们选择第二种方式定义状态表:

我们使用 dp[i][j] 表示到达 [i, j] 位置时,所有下降路径中的最小和。在考虑到达 [i, j] 的三种方式时,即从正上方 [i - 1, j]、左上方 [i - 1, j - 1] 和右上方 [i - 1, j + 1] 转移到 [i, j] 位置,我们需要选择三者中的最小值,再加上矩阵在 [i, j] 位置的值。因此,状态转移方程为:

dp[i][j]=matrix[i-1][j-1]+min(dp[i-1][j-1],min(dp[i-1][j],dp[i-1][j+1]));

在初始化过程中,我们添加一个辅助结点,将其值初始化为正无穷大,以保证后续填表时是正确的。同时,需要注意下标的映射关系。在本题中,我们添加了一行和两列,将第一行的值初始化为 0。填表的顺序是从上往下逐行填写。最后,我们不是返回 dp[m][n] 的值,而是返回 dp 表中最后一行的最小值,因为题目要求只要到达最后一行即可。

代码

class Solution {
public:int minFallingPathSum(vector<vector<int>>& matrix) {int m=matrix.size(),n=matrix[0].size();vector<vector<int>> dp(n+1,vector<int>(n+2,INT_MAX));for(int i=0;i<n+2;i++) dp[0][i]=0;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)dp[i][j]=matrix[i-1][j-1]+min(dp[i-1][j-1],min(dp[i-1][j],dp[i-1][j+1]));int ret=INT_MAX;for(int i=1;i<=n;i++)ret=min(ret,dp[n][i]);return ret;}
};

05.最小路径和

题目链接:https://leetcode.cn/problems/minimum-path-sum/

给定一个包含非负整数的 *m* x *n* 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

**说明:**每次只能向下或者向右移动一步。

示例 1:

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。

示例 2:

输入:grid = [[1,2,3],[4,5,6]]
输出:12

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 200
  • 0 <= grid[i][j] <= 200

思路

在处理这种路径类问题时,我们通常选择两种状态表现形式:一是从某个位置出发,描述到达其他位置的情况;二是从起始位置到达某个位置,描述达到该位置时的状态。在这里,我们选择第二种方式定义状态表:

我们使用 dp[i][j] 表示到达 [i, j] 位置处的最小路径和。在分析 dp[i][j] 的情况时,我们考虑到达 [i, j] 位置之前的一小步有两种情况:一是从上方 [i - 1, j] 向下走一步,转移到 [i, j] 位置;二是从左方 [i, j - 1] 向右走一步,转移到 [i, j] 位置。由于我们要找的是最小路径,因此只需要这两种情况下的最小值,再加上 [i, j] 位置上本身的值即可。

也就是说,状态转移方程为:dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1];

在初始化过程中,我们可以在最前面加上一个「辅助结点」,帮助我们初始化。使用这种技巧需要注意两个点:一是辅助结点里面的值要保证后续填表是正确的;二是下标的映射关系。在本题中,添加了一行和一列,所有位置的值可以初始化为无穷大,然后让 dp[0][1] = dp[1][0] = 1 即可。

填表的顺序是从上往下逐行填写,每一行从左往右。最后,我们返回 dp 表中最后一个位置的值,即 dp[m][n]

代码

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

06.地下城游戏

题目链接:https://leetcode.cn/problems/dungeon-game/

恶魔们抓住了公主并将她关在了地下城 dungeon右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。

骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。

有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。

为了尽快解救公主,骑士决定每次只 向右向下 移动一步。

返回确保骑士能够拯救到公主所需的最低初始健康点数。

**注意:**任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。

示例 1:

输入:dungeon = [[-2,-3,3],[-5,-10,1],[10,30,-5]]
输出:7
解释:如果骑士遵循最佳路径:右 -> 右 -> 下 -> 下 ,则骑士的初始健康点数至少为 7 。

示例 2:

输入:dungeon = [[0]]
输出:1 

提示:

  • m == dungeon.length
  • n == dungeon[i].length
  • 1 <= m, n <= 200
  • -1000 <= dungeon[i][j] <= 1000

思路

这道题可以通过动态规划求解,首先需要定义状态表现形式。如果我们定义为“从起点开始,到达 [i, j] 位置的时候,所需的最低初始健康点数”,分析状态转移时可能会受到后续路径的影响。因此,更合适的状态表现形式是“从 [i, j] 位置出发,到达终点时所需要的最低初始健康点数”。

综上,我们定义状态表达为:dp[i][j]表示:从 [i, j] 位置出发,到达终点时所需的最低初始健康点数。

在状态转移方程中,我们考虑从 [i, j] 位置出发的两种选择: i. 向右走到终点,即从 [i, j] 到 [i, j + 1]; ii. 向下走到终点,即从 [i, j] 到 [i + 1, j]。

对于这两种选择,我们需要选择使得到达终点时的初始健康点数最小的路径。因此,状态转移方程为: dp[i][j]=min(dp[i+1][j],dp[i][j+1])-dungeon[i][j];

然而,由于 dungeon[i][j] 可能是一个较大的正数,计算得到的dp[i][j]的值可能会小于等于 0。如果初始健康点数小于等于 0,马上死亡,因此我们需要处理这种情况,将 dp[i][j] 与 1 取最大值:dp[i][j]=max(1,dp[i][j]);

在初始化阶段,我们在最前面加上一个“辅助结点”来帮助初始化,需要注意辅助结点里面的值要保证后续填表是正确的,以及下标的映射关系。在本题中,我们在 dp 表的最后一行和最后一列分别添加一行和一列,将所有的值初始化为无穷大,然后让 dp[m][n - 1] = dp[m - 1][n] = 1

填表的顺序是从下往上逐行填写,每一行从右往左。最后,我们返回 dp[0][0] 的值。

代码

class Solution {
public:int calculateMinimumHP(vector<vector<int>>& dungeon) {int m=dungeon.size(),n=dungeon[0].size();vector<vector<int>> dp(m+1,vector<int>(n+1,INT_MAX));dp[m][n-1]=dp[m-1][n]=1;for(int i=m-1;i>=0;i--)for(int j=n-1;j>=0;j--){dp[i][j]=min(dp[i+1][j],dp[i][j+1])-dungeon[i][j];dp[i][j]=max(1,dp[i][j]);}return dp[0][0];}
};

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

KubeSphere 镜像构建器(S2I)服务证书过期解决方案

目前 KubeSphere 所有 3.x.x 版本&#xff0c;如果开启了 DevOps 模块并使用了镜像构建器功能&#xff08;S2I&#xff09;都会遇到证书过期问题。 解决方法 已开启 DevOps 模块 下载这个更新 S2I 服务证书压缩包&#xff0c;上传到任一可以访问 K8s 集群的节点&#xff1b; …

一键解锁本地大型语言模型!Ollama框架让你轻松运行Gemma

想要在本地运行大型语言模型吗&#xff1f; Ollama框架提供了这样的机会。 这个框架是专为在Docker容器中部署LLM而设计的&#xff0c;简化了部署和管理流程。 安装Ollama后&#xff0c;你只需执行一条命令&#xff0c;即可在本地运行开源大型语言模型。 它将模型权重、配置…

数据可视化基础与应用-01-数据可视化概述

总结 本系列是数据可视化基础与应用的第02篇&#xff0c;主要介绍数据可视化概述&#xff0c;包括数据可视化的历史&#xff0c;原理&#xff0c;工具等。 认识大数据可视化 数据是什么 信息科学领域面临的一个巨大挑战是数据爆炸。据IDC Global DataSphere统计&#xff0c…

LeetCode 第一题: 两数之和

文章目录 第一题: 两数之和题目描述示例 解题思路Go语言实现 - 一遍哈希表法C实现算法分析 排序和双指针法Go语言实现 - 排序和双指针法C算法分析 暴力法Go语言实现 - 暴力法C算法分析 二分搜索法Go语言实现 - 二分搜索法C算法分析 第一题: 两数之和 ‍ 题目描述 给定一个整…

JS基础之常用调试语句

JS基础之常用调试语句 目录 JS基础之常用调试语句打印控制台consolelog日志级别error错误信息warn警告级别 警告框alert输入框prompt 打印控制台console log日志级别 <script>let name "张三"console.log(name) </script>error错误信息 <script&g…

二维动态规划题目(未完)

1.最小路径和&#xff08;力扣LCR 99题&#xff09; 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;一个机器人每次只能向下或者向右移动一步。 方法一&#xff1a;暴力递归…

软考46-上午题-【数据库】-数据查询语言DQL1

一、SQL数据查询功能 SELECT语句的语法如下&#xff1a; 【注意】&#xff1a; 使用DISTINCT选项可以去重&#xff1b; form子句中出现多个基本表或视图时&#xff0c;系统首先执行笛卡尔积操作。 下面的查询示例均以这些表为基础 1-1、投影查询-SELECT 【回顾】&#xff1a;…

自定义神经网络三之梯度和损失函数激活函数

文章目录 前言梯度概述梯度下降算法梯度下降的过程 optimize优化器 梯度问题梯度消失梯度爆炸 损失函数常用的损失函数损失函数使用原则 激活函数激活函数和损失函数的区别激活函数Relu-隐藏层激活函数Sigmoid和Tanh-隐藏层Sigmoid函数Tanh&#xff08;双曲正切&#xff09; &l…

基于Pytorch的猫狗图片分类【深度学习CNN】

猫狗分类来源于Kaggle上的一个入门竞赛——Dogs vs Cats。为了加深对CNN的理解&#xff0c;基于Pytorch复现了LeNet,AlexNet,ResNet等经典CNN模型&#xff0c;源代码放在GitHub上&#xff0c;地址传送点击此处。项目大纲如下&#xff1a; 文章目录 一、问题描述二、数据集处理…

单片机一个32位地址对应多大的存储空间?

文章目录 文字图片 文字 一个地址是4个字节 一个地址对应一个字节的存储空间&#xff08;无论8位、16位、32位单片机&#xff09; 学过C语言的都知道&#xff1a;指针就是地址&#xff0c;因此指针也是4个字节 图片 这两张是工作的笔记、主要看第二张&#xff0c;左边是代码&…

第7.1章:StarRocks性能调优——查询分析

目录 一、查看查询计划 1.1 概述 1.2 查询计划树 1.3 查看查询计划的命令 1.3 查看查询计划 二、查看查询Profile 2.1 启用 Query Profile 2.2 获取 Query Profile 2.3 Query Profile结构与详细指标 2.3.1 Query Profile的结构 2.3.2 Query Profile的合并策略 2.…

单链表详解

个人主页&#xff1a;不爱学英文的码字机器-CSDN博客 收录合集&#xff1a;《数据结构》 在本篇博客中&#xff0c;我们将深入探讨单链表的定义、实现和应用。 本篇博客将用C语言实现的单链表进行讲解&#xff0c;通过一段代码一段讲解来逐个详细讲解&#xff0c;深入了解单链表…