BM61-矩阵最长递增路径

news/2024/10/21 10:00:21/

题目

给定一个 n 行 m 列矩阵 matrix ,矩阵内所有数均为非负整数。 你需要在矩阵中找到一条最长路径,使这条路径上的元素是递增的。并输出这条最长路径的长度。

这个路径必须满足以下条件:

  1. 对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外。
  2. 你不能走重复的单元格。即每个格子最多只能走一次。

数据范围:1≤n,m≤1000,0≤matrix[i][j]≤1000。

进阶:空间复杂度 O(nm),时间复杂度 O(nm)。

例如:当输入为[[1,2,3],[4,5,6],[7,8,9]]时,对应的输出为5,其中的一条最长递增路径如下图所示:

示例1

输入:[[1,2,3],[4,5,6],[7,8,9]]

返回值:5

说明:1->2->3->6->9即可。当然这种递增路径不是唯一的。

示例2

输入:[[1,2],[4,3]]

返回值:4

说明: 1->2->3->4

备注:矩阵的长和宽均不大于1000,矩阵内每个数不大于1000。


思路1:深度优先搜索(dfs)(推荐使用)

深度优先搜索一般用于树或者图的遍历,其他有分支的(如二维矩阵)也适用。

它的原理是从初始点开始,一直沿着同一个分支遍历,直到该分支结束,然后回溯到上一级继续沿着一个分支走到底,如此往复,直到所有的节点都有被访问到。

既然是查找最长的递增路径长度,那我们首先要找到这个路径的起点,起点不好直接找到,就从上到下从左到右遍历矩阵的每个元素。然后以每个元素都可以作为起点查找它能到达的最长递增路径。

如何查找以某个点为起点的最长递增路径呢?我们可以考虑深度优先搜索,因为我们查找递增路径的时候,每次选中路径一个点,然后找到与该点相邻的递增位置,相当于进入这个相邻的点,继续查找递增路径,这就是递归的子问题。因此递归过程如下:

  • 终止条件: 进入路径最后一个点后,四个方向要么是矩阵边界,要么没有递增的位置,路径不能再增长,返回上一级。
  • 返回值: 每次返回的就是本级之后的子问题中查找到的路径长度加上本级的长度。
  • 本级任务: 每次进入一级子问题,先初始化后续路径长度为0,然后遍历四个方向(可以用数组表示,下标对数组元素的加减表示去往四个方向),进入符合不是边界且在递增的邻近位置作为子问题,查找子问题中的递增路径长度。因为有四个方向,所以最多有四种递增路径情况,因此要维护当级子问题的最大值。

具体做法:

  • step 1:使用一个dp数组记录i,j处的单元格拥有的最长递增路径,这样在递归过程中如果访问到就不需要重复访问。
  • step 2:遍历矩阵每个位置,都可以作为起点,并维护一个最大的路径长度的值。
  • step 3:对于每个起点,使用dfs查找最长的递增路径:只要下一个位置比当前的位置数字大,就可以深入,同时累加路径长度。

代码1

import java.util.*;public class Solution {//记录4个方向private int[][] dirs = new int[][] {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};private int n, m;public int solve (int[][] matrix) {//边界条件判断if (matrix.length == 0 || matrix[0].length == 0) {return 0;}int res = 0;n = matrix.length;m = matrix[0].length;//j,j处的单元格拥有的最长递增路径int[][] dp = new int[m + 1][n + 1];for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {//更新最大值res = Math.max(res, dfs(matrix, dp, i, j));}}return res;}//深度优先搜索,返回最大单元格数public int dfs(int[][] matrix, int[][] dp, int i, int j) {if (dp[i][j] != 0) {return dp[i][j];}dp[i][j]++;for(int k = 0; k < 4; k++) {int nexti = i + dirs[k][0];int nextj = j + dirs[k][1];//判断下一个要遍历的位置是否满足条件:在矩阵范围内且满足路径递增if(nexti >= 0 && nexti < n && nextj >= 0 && nextj < m && matrix[nexti][nextj] > matrix[i][j]) {dp[i][j] = Math.max(dp[i][j], dfs(matrix, dp, nexti, nextj) + 1);}}return dp[i][j];}
}
  • 时间复杂度:O(mn),m、n分别为矩阵的两边,遍历整个矩阵以每个点作为起点,然后递归相当于遍历填充dp矩阵。
  • 空间复杂度:O(mn),辅助矩阵的空间是一个二维数组。

思路2:广度优先搜索(bfs)

广度优先搜索与深度优先搜索不同,它是将与某个节点直接相连的其它所有节点依次访问一次之后,再往更深处,进入与其他节点直接相连的节点。

bfs的时候我们常常会借助队列的先进先出,因为从某个节点出发,我们将与它直接相连的节点都加入队列,它们优先进入,则会优先弹出,在它们弹出的时候再将与它们直接相连的节点加入,由此就可以依次按层访问。

我们可以将矩阵看成是一个有向图,一个元素到另一个元素递增,代表有向图的箭头。这样我们可以根据有向图的出度入度找到最长的路径,且这个路径在矩阵中就是递增的。

具体做法:

  • step 1:计算每个节点(单元格)所对应的出度(符合边界条件且递增),对于作为边界条件的单元格,它的值比所有的相邻单元格的值都要大,因此作为边界条件的单元格的出度都为0。利用一个二维矩阵记录每个单元格的出度
  • step 2:利用拓扑排序的思想,从所有出度为0的单元格开始进行广度优先搜索。
  • step 3:借助队列来广度优先搜索,队列中每次加入出度为0的点,即路径最远点,每次从A点到B点,便将A点出度减一。
  • step 4:每次搜索都会遍历当前层的所有单元格,更新其余单元格的出度,并将出度变为0的单元格加入下一层搜索。
  • step 5:当搜索结束时,搜索的总层数即为矩阵中的最长递增路径的长度,因为bfs的层数就是路径增长的层数。

代码

import java.util.*;public class Solution {//记录4个方向private int[][] dirs = new int[][] {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};private int n, m;public int solve (int[][] matrix) {//边界条件判断if (matrix.length == 0 || matrix[0].length == 0) {return 0;}int res = 0;n = matrix.length;m = matrix[0].length;//j,j处的单元格拥有的最长递增路径int[][] outdegrees = new int[m + 1][n + 1];for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {for (int k = 0; k < 4; k++) {int nexti = i + dirs[k][0];int nextj = j + dirs[k][1];if (nexti >= 0 && nexti < n && nextj >= 0 && nextj < m &&matrix[nexti][nextj] > matrix[i][j]) {//符合条件,记录出度outdegrees[i][j]++;}}}}//辅助队列Queue<Integer> q1 = new LinkedList<Integer>();Queue<Integer> q2 = new LinkedList<Integer>();for (int i = 0; i < n; i++) {for (int j = 0; j < m; j++) {if (outdegrees[i][j] == 0) {//找到出度为0的入队q1.offer(i);q2.offer(j);}}}while (!q1.isEmpty()) {res++;int size = q1.size();for (int x = 0; x < size; x++) {int i = q1.poll();int j = q2.poll();//四个方向for (int k = 0; k < 4; k++) {int nexti = i + dirs[k][0];int nextj = j + dirs[k][1];//逆向搜索,所以下一步是小于if (nexti >= 0 && nexti < n && nextj >= 0 && nextj < m &&matrix[nexti][nextj] < matrix[i][j]) {//符合条件,出度递减outdegrees[nexti][nextj]--;if (outdegrees[nexti][nextj] == 0) {q1.offer(nexti);q2.offer(nextj);}}}}}return res;}
}
  • 时间复杂度:O(mn),m、n分别为矩阵的两边,相当于遍历整个矩阵两次。
  • 空间复杂度:O(mn),辅助矩阵的空间是一个二维数组。

http://www.ppmy.cn/news/63947.html

相关文章

LeetCode周赛复盘(第344场周赛)

文章目录 1、找出不同元素数目差数组1.1 题目链接1.2 题目描述1.3 解题代码1.4 解题思路 2、频率跟踪器2.1 题目链接2.2 题目描述2.3 解题代码2.4 解题思路 3、6418. 有相同颜色的相邻元素数目3.1 题目链接3.2 题目描述3.3 解题代码3.4 解题思路 4、使二叉树所有路径值相等的最…

Java使用HTTP隧道

Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 面向对象程序设计语言和 Java 平台的总称。由 James Gosling和同事们共同研发&#xff0c;并在 1995 年正式推出。 后来 Sun 公司被 Oracle &#xff08;甲骨文&#xff09;公司收购&#xff0c;Java 也随之成为 O…

数据预处理简单介绍,并给出具体的代码示例

深度学习数据预处理简单介绍 深度学习的数据预处理包括以下几个方面&#xff1a; 数据读取和加载&#xff1a;将数据从存储介质中读取到内存中&#xff0c;用于后续的处理和训练。数据清洗和去噪&#xff1a;对数据进行处理&#xff0c;修复缺失值和错误&#xff0c;去除噪声…

C++好难(3):类和对象(中篇)

【本章目标】 类的6个默认成员函数构造函数析构函数拷贝构造函数赋值运算符重载const成员函数取地址及const取地址操作符重载 目录 【本章目标】 1.类的6个默认成员函数 2.构造函数 2.1概念 2.2构造函数的特性 特性一 特性二 特性三 特性四 特性五 特性六 特性七 …

【Proteus仿真】| 05——问题记录

系列文章目录 【Proteus仿真】| 01——软件安装 【Proteus仿真】| 02——基础使用 【Proteus仿真】| 03——超详细使用教程 【Proteus仿真】| 04——绘制原理图模板 【Proteus仿真】| 05——问题记录 文章目录 前言1、51单片机仿真2、stm32仿真1. stm32 adc 采集电压一直为0 3、…

R语言丨根据VCF文件自动填充对其变异位点并生成序列fa文件

根据VCF文件自动填充对其变异位点并生成序列fa文件 首先提出一个问题&#xff1a; 假如有一个重测序结果VCF文件&#xff0c;里面包含了很多个样本在几百个突变位点&#xff08;snp和iad&#xff09;的基因型数据&#xff0c;现在想根据这份原始数据&#xff0c;得到一个fasta序…

争夺汽车芯片「高地」

一直以来&#xff0c;汽车芯片无论是工艺制程&#xff0c;还是新技术的导入&#xff0c;都要落后消费类产品几年时间。不过&#xff0c;如今&#xff0c;随着汽车智能化进一步推动汽车制造商与上游芯片设计公司、晶圆代工厂的紧密互动&#xff0c;历史即将翻篇。 同时&#xf…

keil MDK5软件包介绍、下载、安装与分享

前言 本文介绍了Keil MDK5软件包的分类、作用、下载、安装与更新。软件包下载可通过Keil自带的Pack Installer、进入Keil Pack下载网站手动下载、去芯片厂家官网下载三种方式。同时分享了一个小技巧&#xff0c;可以直接分享已安装好的软件包给别人。 一. Keil MDK软件包介绍 K…