代码随想录算法训练营第三十三天

LeetCode.62 不同路径

题目链接 不同路径

题解

class Solution {public int uniquePaths(int m, int n) {// dp表示到达ij有多少条路径int[][] dp = new int[110][110];dp[1][1] = 1;for(int i = 0;i<m;i++){dp[i][0] = 1;}for(int j = 0;j<n;j++){dp[0][j] = 1;}for(int i = 1;i<m;i++){for(int j = 1;j<n;j++){dp[i][j] = dp[i-1][j] + dp[i][j-1];}}return dp[m-1][n-1];}
}

解题思路

这段代码是用来解决不同路径问题的动态规划实现。下面我将详细解析这段代码的思路、实现细节以及可能的优化点。

一、问题背景:不同路径问题

一个机器人位于一个 m x n 网格的左上角(起始点在下图中标记为 “Start”)。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。问总共有多少条不同的路径?

例如,当 m=3,n=7 时,总共有 28 条不同路径。

二、代码思路解析

1. 动态规划定义

  • dp[i][j] 表示:从左上角 (0,0) 出发,到达网格 (i,j) 时的不同路径总数

2. 边界条件

  • 当 i=0(第一行)时,机器人只能一直向右移动,所以到达 (0,j) 的路径只有 1 条,即 dp[0][j] = 1
  • 当 j=0(第一列)时,机器人只能一直向下移动,所以到达 (i,0) 的路径只有 1 条,即 dp[i][0] = 1
  • 代码中初始化了 dp[1][1] = 1,但实际通过后续对第一行和第一列的初始化,这个值会被覆盖(若 m 和 n 大于 1),可以忽略。

3. 状态转移方程

  • 对于网格中任意一点 (i,j)(非第一行、非第一列),机器人只能从上方 (i-1,j) 或左方 (i,j-1) 移动而来。
  • 因此,到达 (i,j) 的路径数 = 到达 (i-1,j) 的路径数 + 到达 (i,j-1) 的路径数,即:
    dp[i][j] = dp[i-1][j] + dp[i][j-1]

4. 遍历顺序

  • 从 i=1 到 i=m-1(行遍历),从 j=1 到 j=n-1(列遍历),依次计算每个网格的路径数,最终 dp[m-1][n-1] 即为答案。

三、代码细节说明

  1. 数组初始化
    int[][] dp = new int[110][110];
    这里创建了一个 110x110 的二维数组,足够覆盖题目中常见的 m 和 n 范围(通常题目约束 m,n <= 100)。

  2. for(int i = 0;i<m;i++){dp[i][0] = 1;  // 第一列全为1
    }
    for(int j = 0;j<n;j++){dp[0][j] = 1;  // 第一行全为1
    }
    
     

    这两个循环正确初始化了边界条件,确保第一行和第一列的路径数均为 1。

  3. 状态转移计算

    for(int i = 1;i<m;i++){for(int j = 1;j<n;j++){dp[i][j] = dp[i-1][j] + dp[i][j-1];}
    }
    
     

    双层循环遍历网格的非边界部分,通过上方和左方的路径数累加得到当前网格的路径数。

  4. 返回结果
    return dp[m-1][n-1];
    由于数组下标从 0 开始,右下角的坐标为 (m-1, n-1),直接返回该位置的路径数即可。

四、测试案例验证

以 m=3,n=7 为例:

  • 初始化第一行 dp[0][0..6] = [1,1,1,1,1,1,1],第一列 dp[0..2][0] = [1,1,1]
  • 计算 dp[1][1] = dp[0][1] + dp[1][0] = 1 + 1 = 2
  • 计算 dp[1][2] = dp[0][2] + dp[1][1] = 1 + 2 = 3
  • ... 依次类推,最终 dp[2][6] = 28,与预期结果一致。

LeetCode.63. 不同路径 II

题目链接 不同路径 II

题解

class Solution {public int uniquePathsWithObstacles(int[][] obstacleGrid) {int m = obstacleGrid.length;int n = obstacleGrid[0].length;int[][] dp = new int[110][110];for(int i = 0;i<m;i++){if(obstacleGrid[i][0] == 1) {break;}dp[i][0] = 1;}for(int j = 0;j<n;j++){if(obstacleGrid[0][j] == 1) {break;}dp[0][j] = 1;}for(int i = 1;i<m;i++){for(int j = 1;j<n;j++){dp[i][j] = dp[i-1][j] + dp[i][j-1];if(obstacleGrid[i][j] == 1) dp[i][j] = 0;}}return dp[m-1][n-1];}
}

解题思路

这段代码是用来解决带障碍物的不同路径问题的动态规划实现。相比基础的不同路径问题,这个问题增加了障碍物的限制,我们来详细分析这段代码的思路和实现细节。

一、问题背景:带障碍物的不同路径

一个机器人位于一个 m x n 网格的左上角(起始点)。机器人每次只能向下或者向右移动一步。网格中可能存在障碍物,机器人不能通过障碍物。求从左上角到右下角总共有多少条不同的路径?

例如,对于下面的网格(1 表示障碍物):

[[0,0,0],[0,1,0],[0,0,0]
]

答案是 2,因为有两条不同的路径可以避开障碍物到达终点。

二、代码思路解析

1. 动态规划定义

  • dp[i][j] 表示:从左上角 (0,0) 出发,到达网格 (i,j) 时的不同路径总数(若 (i,j) 是障碍物,则路径数为 0)。

2. 边界条件处理

  • 对于第一列(j=0):
    • 从 i=0 开始遍历,如果遇到障碍物(obstacleGrid[i][0] == 1),则后续所有单元格都无法到达(直接 break)。
    • 否则,dp[i][0] = 1(只能从上方一直向下移动到达)。
  • 对于第一行(i=0):
    • 从 j=0 开始遍历,如果遇到障碍物(obstacleGrid[0][j] == 1),则后续所有单元格都无法到达(直接 break)。
    • 否则,dp[0][j] = 1(只能从左方一直向右移动到达)。

3. 状态转移方程

  • 对于非边界的单元格 (i,j)
    • 先计算正常情况下的路径数:dp[i][j] = dp[i-1][j] + dp[i][j-1](从上方或左方到达)。
    • 如果当前单元格是障碍物(obstacleGrid[i][j] == 1),则路径数为 0,即 dp[i][j] = 0

4. 最终结果

  • 返回 dp[m-1][n-1],即到达右下角的路径总数。

三、代码细节说明

  1. 初始化网格大小

    int m = obstacleGrid.length;
    int n = obstacleGrid[0].length;
    
     

    通过输入的障碍物网格获取行数 m 和列数 n

  2. DP 数组初始化

    int[][] dp = new int[110][110];
    
     

    创建一个 110x110 的二维数组,足够覆盖常见的网格大小约束。

  3. 第一列边界处理

    for(int i = 0;i<m;i++){if(obstacleGrid[i][0] == 1) {break;  // 遇到障碍物,后续单元格都无法到达}dp[i][0] = 1;
    }
    
     

    从顶部开始向下遍历第一列,一旦遇到障碍物,就终止循环,确保障碍物下方的单元格路径数保持默认的 0。

  4. 第一行边界处理

    for(int j = 0;j<n;j++){if(obstacleGrid[0][j] == 1) {break;  // 遇到障碍物,后续单元格都无法到达}dp[0][j] = 1;
    }
    
     

    从左侧开始向右遍历第一行,逻辑与第一列处理类似。

  5. 非边界单元格计算

    for(int i = 1;i<m;i++){for(int j = 1;j<n;j++){dp[i][j] = dp[i-1][j] + dp[i][j-1];  // 先计算正常路径数if(obstacleGrid[i][j] == 1) dp[i][j] = 0;  // 若有障碍物则路径数为0}
    }
    
     

    双层循环遍历网格的非边界部分,先按正常逻辑累加路径数,再判断当前位置是否为障碍物并修正路径数。

四、测试案例验证

以示例网格为例:

[[0,0,0],[0,1,0],[0,0,0]
]

  • 初始化第一列:dp[0][0]=1dp[1][0]=1dp[2][0]=1(无障碍物)。
  • 初始化第一行:dp[0][0]=1dp[0][1]=1dp[0][2]=1(无障碍物)。
  • 计算 dp[1][1]:先算 dp[0][1] + dp[1][0] = 2,但因 (1,1) 是障碍物,最终 dp[1][1] = 0
  • 计算 dp[1][2]dp[0][2] + dp[1][1] = 1 + 0 = 1(无障碍物,结果为 1)。
  • 计算 dp[2][1]dp[1][1] + dp[2][0] = 0 + 1 = 1(无障碍物,结果为 1)。
  • 计算 dp[2][2]dp[1][2] + dp[2][1] = 1 + 1 = 2(最终答案为 2)。

结果与预期一致,验证了代码的正确性。

LeetCode.96 不同的二叉搜索树

题目链接 不同的二叉搜索树

题解

class Solution {public int numTrees(int n) {int[] dp = new int[25];dp[0] = 1;for(int i = 1;i<=n;i++){for(int j = 1;j<=i;j++){dp[i] += dp[j-1] * dp[i-j]; }}return dp[n];}
}

解题思路

这段代码实现了计算不同结构的二叉搜索树数量的功能,使用的是动态规划的思想。

代码解析:

  1. 定义了一个numTrees方法,接收一个整数n作为参数,返回值是int类型,表示 n 个节点能组成的不同二叉搜索树的数量。

  2. 创建了一个长度为 25 的数组dp,用于存储中间计算结果。dp[i]表示 i 个节点能组成的不同二叉搜索树的数量。

  3. 初始化dp[0] = 1,这是一个边界条件,表示 0 个节点时只有 1 种情况(空树)。

  4. 使用两层循环计算dp数组:

    • 外层循环i从 1 到 n,表示计算 i 个节点的情况
    • 内层循环j从 1 到 i,表示以 j 为根节点的情况
    • 当以 j 为根节点时,左子树有 j-1 个节点,右子树有 i-j 个节点
    • 因此dp[i]等于所有可能的左子树数量乘以右子树数量的和
  5. 最后返回dp[n],即 n 个节点能组成的不同二叉搜索树的数量

这个问题其实是一个经典的卡特兰数(Catalan number)问题,代码通过动态规划的方式计算出了第 n 个卡特兰数,也就是 n 个节点所能组成的不同二叉搜索树的数量。

总结

以上三个问题均用动态规划求解。62 题算 m×n 网格不同路径数,dp [i][j] 为到 (i,j) 的路径数,边界为首行首列全 1,转移方程为 dp [i][j] = 左 + 上。63 题加障碍物,遇障后首行 / 列后续为 0,当前有障则 dp 为 0。96 题算 n 个节点二叉搜索树数,是卡特兰数问题,dp [i] 为 i 个节点的树数,转移方程为左子树数 × 右子树数之和。

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

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

相关文章

银行回单OCR识别技术原理

银行回单OCR&#xff08;光学字符识别&#xff09;技术通过结合图像处理、模式识别和自然语言处理&#xff08;NLP&#xff09;技术&#xff0c;将纸质或电子版银行回单中的非结构化文本&#xff08;如账号、金额、日期等&#xff09;转化为结构化数据。以下是其核心原理和关键…

Day22-二叉树的迭代遍历

昨天学习了递归遍历&#xff1a;递归就是一次次的把参数压入栈中&#xff0c;然后返回的时候还是上一次递归保存的参数。今天学习迭代遍历。迭代遍历就是用栈去模拟保存二叉树的节点&#xff0c;然后依次去遍历&#xff0c;只不过要注意栈的后入先出的规则。前序遍历&#xff1…

知识蒸馏 - 通过引入温度参数T调整 Softmax 的输出

知识蒸馏 - 通过引入温度参数T调整 Softmax 的输出 flyfish import torch import torch.nn.functional as F import matplotlib.pyplot as plt import numpy as np# 设置中文字体支持 plt.rcParams["font.family"] [AR PL UMing CN] # Linux plt.rcParams[axes.uni…

Java研学-RabbitMQ(三)

一 消息通信协议 1 AMQP AMQP 是一个开放的、跨语言、跨平台的消息协议标准&#xff0c;用于在分布式系统中传递业务消息。它定义了消息队列的二进制协议格式和交互模型&#xff08;如交换机、队列、绑定等&#xff09;&#xff0c;确保不同语言&#xff08;Java、Python、C#等…

http.client 教程-如何使用 Python 标准库发送 HTTP 请求

http.client 教程-如何使用 Python 标准库发送 HTTP 请求以下是 http.client 模块的详细使用教程&#xff0c;帮助你理解如何使用 Python 标准库发送 HTTP 请求&#xff1a;1. http.client 概述http.client 是 Python 内置的 HTTP 客户端库&#xff0c;提供了底层的 HTTP 协议实…

Android-三种持久化方式详解

持久化技术分为3种&#xff0c;文件&#xff0c;sharedPreferences存储&#xff0c;数据库来存储&#xff1b; 目录 文件存储&#xff1a; 利用SharedPreferences中读取数据 SQLite创建数据库 更新 添加 删除 查找&#xff1a; 文件存储&#xff1a; 文件存储是 Andr…

并发安全之锁机制一

锁机制一 锁机制是计算机系统中解决并发冲突的核心工具&#xff0c;其存在和应用场景源于一个根本问题&#xff1a;当多个执行单元&#xff08;线程、进程、分布式节点&#xff09;同时访问或修改同一份共享资源时&#xff0c;如何保证数据的正确性、一致性和系统可靠性&#x…

结合项目阐述 设计模式:单例、工厂、观察者、代理

原文链接&#xff1a;https://download.csdn.net/blog/column/12433305/133862792#_1613 1、工厂模式应用 C17及之后可编译 /*日志落地模块的实现1.抽象落地基类2.派生子类&#xff08;根据不同落地方向进行派生&#xff09;3.使用工厂模式进行创建与表示的分离 */#ifndef _…

uniapp 更新apk有缓存点不动,卸载安装apk没有问题。android

方式一。pages.json&#xff1a;"globalStyle" : {"navigationBarTextStyle" : "black","navigationBarTitleText" : "uni-app","navigationBarBackgroundColor" : "#F8F8F8","backgroundColor&qu…

HTML响应式SEO公司网站源码

核心优势 100%纯HTML/CSS开发自动适配手机/平板/PC内置SEO优化结构0.5秒极速加载 包含页面 • 首页&#xff08;关键词布局优化版&#xff09; • 服务项目展示页 • 客户案例库 • 新闻资讯系统 • 联系方式&#xff08;带地图API&#xff09; 技术参数 兼容Chrome/Firefo…

Error: llama runner process has terminated: exit status 2

我是i7 12700h ,3080显卡&#xff0c;在 Windows 11 上运行 ollama run deepseek-r1:1.5b 出现 Error: llama runner process has terminated: exit status 2 之前是好用的&#xff0c;后来不知为什么就不好用了。 原因&#xff1a; 检查 Microsoft Visual C Redistributab…

Linux中ssh远程登录原理与配置

SSH连接的五个阶段 1. 版本协商阶段&#xff08;Protocol Version Negotiation&#xff09;目的&#xff1a;协商使用SSH-1或SSH-2协议&#xff08;现代系统默认SSH-2&#xff09;。流程&#xff1a;关键点&#xff1a;若版本不兼容&#xff08;如客户端只支持SSH-1&#xff0c…

Kubernetes --存储入门

一、Volume 的概念对于大多数的项目而言&#xff0c;数据文件的存储是非常常见的需求&#xff0c;比如存储用户上传的头像、文件以及数据库的数据。在 Kubernetes 中&#xff0c;由于应用的部署具有高度的可扩展性和编排能力&#xff08;不像传统架构部署在固定的位置&#xff…

蚂蚁 KAG 框架开源:知识图谱 + RAG 双引擎

引言&#xff1a;从RAG到KAG&#xff0c;专业领域知识服务的技术突破 在大语言模型&#xff08;LLM&#xff09;应用落地过程中&#xff0c;检索增强生成&#xff08;RAG&#xff09; 技术通过引入外部知识库有效缓解了模型幻觉问题&#xff0c;但在专业领域仍面临三大核心挑战…

V-Ray 7.00.08 for 3ds Max 2021-2026 安装与配置教程(含语言补丁)

本文介绍 V-Ray 7.00.08 渲染器在 3ds Max 2021-2026 各版本中的安装与使用配置步骤&#xff0c;适合需要进行可视化渲染工作的设计师、建筑师及相关从业者。附带语言补丁配置方式&#xff0c;帮助用户获得更顺畅的使用体验。 &#x1f4c1; 一、安装文件准备 软件名称&#xf…

Go-Elasticsearch Typed Client查询请求的两种写法强类型 Request 与 Raw JSON

1 为什么需要两种写法&#xff1f; 在 Golang 项目中访问 Elasticsearch&#xff0c;一般会遇到两类需求&#xff1a;需求场景特点最佳写法后台服务 / 业务逻辑查询固定、字段清晰&#xff0c;需要编译期保障Request 结构体仪表盘 / 高级搜索 / 模板 DSL查询片段由前端或脚本动…

Leaflet 综合案例-聚类图层控制

看过的知识不等于学会。唯有用心总结、系统记录&#xff0c;并通过温故知新反复实践&#xff0c;才能真正掌握一二 作为一名摸爬滚打三年的前端开发&#xff0c;开源社区给了我饭碗&#xff0c;我也将所学的知识体系回馈给大家&#xff0c;助你少走弯路&#xff01; OpenLayers…

React组件中的this指向问题

在 React 组件中&#xff0c;函数定义方式影响this指向的核心原因是箭头函数与普通函数的作用域绑定规则不同&#xff0c;具体差异如下&#xff1a;​ 1. 普通函数&#xff08;function定义&#xff09;需要手动bind(this)的原因​ 当用function在组件内定义方法时&#xff1…

Vue 项目中的组件引用如何实现,依赖组件间的数据功能交互及示例演示

在 Vue 项目中&#xff0c;组件间的引用与数据交互是核心功能之一。以下是组件引用和数据交互的详细实现方式及示例&#xff1a;一、组件引用方式 1. 基本组件引用 局部注册&#xff1a;在父组件中按需引入子组件并注册。 // ParentComponent.vue import ChildComponent from .…

✨ 使用 Flask 实现头像文件上传与加载功能

文章目录&#x1f9f1; 技术栈&#x1f5c2;️ 项目结构与配置&#x1f510; 用户身份校验逻辑&#x1f4e4; 头像上传接口&#xff1a;/file/avatar/upload&#x1f4e5; 加载头像接口&#xff1a;/file/avatar/load/<filename>&#x1f9ea; 示例请求&#xff08;使用 …