Leetcode 每日一题:Longest Increasing Path in a Matrix

写在前面:

今天我们继续看一道 图论和遍历 相关的题目。这道题目的背景是在一个矩阵当中找寻最长的递增数列长度。思路上非常好想,绝对和 DFS 相关,但是题目的优化要求非常高,对于语言和内存特性的考察特别丰富,如果是单纯的 Leetcoder 或者是 扎根算法而语言相对专精较弱的同学,这道题目可能很难做到 AC,就让我们一起来看看吧!

题目介绍:

题目信息:

  • 题目链接:https://leetcode.com/problems/longest-increasing-path-in-a-matrix/description/
  • 题目类型:Array,Graph,DFS,Dynamic Programming(是的,可以拿 dp 做,但是太难了而且并不具备推广价值,所以不分享了)
  • 题目难度:Hard(思路 easy,优化要求 hard)
  • 题目来源:Google 高频面试真题

题目问题:

  • 给定一个大小为 m * n 的矩阵,每一个矩阵的位置都代表一个 value
  • 找出最长的 递增遍历行走路线
  • 在行走的过程中,只能上下左右,不能走对角线
  • 例子:

题目想法:

深度优先暴力解法:

一看到是走 “最长的递增” 数组,并且在图中只能上下左右移动,我们很容易可以想到 DFS,这也是一个标准的 DFS 问题。我们只需要遍历每一个位置当作起点,并且不断寻找周围有没有递增的位置可以让我们前进,返回当前起点所能达到的最大值。将所有的最大值统筹起来,进行返回。

很可惜,思路很美好,现实很骨感,这样的做法不仅会 TLE,而且会 MLE,在算法时间复杂度和占用空间内存的角度来讲都会爆炸。

Runtime:O(2^(m+n)) --> 我们需要遍历所有可能的子数组

Space:O(mn) --> 我们最坏需要遍历所有的 node 和 edge,并且,需要mn次 stack 来储存

太慢,也太耗空间了(如果懂得堆栈的小伙伴应该知道太多 recursion 会占掉很多内存)

时间复杂度优化:

暴力解法非常简单想到,那 Google 和 其他大厂的要求肯定不止这么一点儿~~ 但如果自己想想,我们每一次以一个点为起点进行探索的时候,我们的目标都是取得这个点所能创造的最大长度,从而决定我们是否有机会获得更好的结果。那我们是否可以利用 Memoization 的方法,将每个遍历过后的点的最大值记下来,这样之后的重复使用不就都不用再遍历了吗?

我们在从每一个点开始遍历 DFS 的时候,将所有路过的点的最大值进行一个更新,而我们在找寻起点的过程中,就可以直接从这些点里取值了,因为他一定是最大的。

为什么一定如果一个点被遍历过之后存下的结果一定 >= 我们以这个点为起点重新遍历呢?

  • 如果这个点的现有最大值是从本点开始,那再来一次遍历也会得到相同结果
  • 如果这个点的现有最大值是从一个比他更小的点得来的,那以他自己为起点此次结果一定会小于我们所存储的节点,因为单调递增的单向性
  • 而这个点的现有最大值不可能来自己于一个比他更大的起点
  • 所以总结,存下的结果 >= 以当前为起点遍历可能最大 ---> 当前存储的结果一定是最优的

Runtime:O(mn) ---> 我们只需要遍历一次图即可,可以被 AC

Space:O(mn) ---> 我们需要存储所有的矩阵点的最大值

内存优化:

虽然这道题的 O(mn) 的空间复杂度已经是最优复杂度了,但不同的 implementation 同样会导致 AC 和 MLE 的区别,题主自己在做这道题的时候,经过了算法的优化以后还是 MLE,后来经过对数据结构和 recursion 变量的优化才最终解决内存问题,这可能也是大厂对代码水平更高难度的考察吧,这也是我认为这道题是 hard 的一部分原因

如果使用 C++ 进行代码编写,可以优化的点有

  • 对于数据的存储,不使用 vector, 而是使用 dynamic array 进行内存分配
  • 对于Recursion函数传入的矩阵和记忆矩阵,把他们变成 member variable,在recursion中静态调用,而不是不停的动态参数传入

第一个问题的原因是基于 vector 的特性。在 C++中,动态数组的内存分布并不是按照有多少个元素来分配的,而是分配 2^n 个储存空间,n 是让 2^n 大于所需长度的最小值。而这样的分配方式会造成内存浪费。例如:一个长度为 7 的 vector 实际上占用了 8 个长度的内存,而长度为 9 的 vector 实际上占用了 16 个长度的内存。而如果使用 dynamic allocation,在 matrix 长度宽度已知不会变的情况下,我们完全可以手动分配内存,精准的将 矩阵的维度 分配出来。在 2 个维度同时减少内存浪费,省去非常多的空间

第二个问题的原因是,没有一次 recursion function call,这个函数就会在内存中开辟一段储存空间,而如果我们将 矩阵和记忆矩阵作为变量不断传入,他们就会不断的在内存 stack 中生成很多很多 copy,从而导致 stack 崩掉。而如果我们将其改成 member variable,他们相对于所有的 recursion 函数就是全局的,每个 recursion 都只需要直接调用他的指针所指的真实数据,而不是 copy,这也会节省更多空间

在同时完成这两个优化以后,相信大家也能成功 AC 这道 Leetcode hard

题目代码:

class Solution {
public:int x[4] = {0, 1, 0, -1};int y[4] = {1, 0, -1, 0};int** maxViewed;      //use dynamic array instead of vector to prevent memory wasteint** matrix;         //use member variable instead of arguement in recursion to prevent //too much stack memory wasted in recursionint DFS(int i, int j, int m, int n){//we have already seen this point, so need to traverse. if(maxViewed[i][j] != 0){return maxViewed[i][j];}for(int d = 0; d < 4; d++){int new_i = i + x[d];int new_j = j + y[d];//check in bound, no need to check revisit, but have check for increasingif(new_i < m && new_i >= 0 && new_j < n && new_j >= 0){if(matrix[i][j] < matrix[new_i][new_j]){maxViewed[i][j] = max(DFS(new_i, new_j, m, n), maxViewed[i][j]);}}}maxViewed[i][j] += 1;return maxViewed[i][j];}int longestIncreasingPath(vector<vector<int>>& matrix_target) {int maxDist = 0;int m = matrix_target.size(), n = matrix_target[0].size();//create a cache and matrix storage for the traversing:maxViewed = new int*[m];  // Allocate memory for rowsmatrix = new int*[m];for (int i = 0; i < m; ++i) {maxViewed[i] = new int[n];  // Allocate memory for columnsmatrix[i] = new int[n];}for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){maxViewed[i][j] = 0;matrix[i][j] = matrix_target[i][j];}}for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){maxDist = max(maxDist, DFS(i, j, m, n));}}return maxDist;}
};

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

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

相关文章

15. Springboot集成Redis

目录 1、前言 2、为什么选择Spring Boot集成Redis&#xff1f; 3、快速上手 3.1、引入依赖 3.2、 配置连接信息 3.3、自定义配置类 4、RedisTemplate的使用 4.1、String类型操作 4.2、 Hash类型操作 4.3、List类型操作 4.4、Set类型操作 4.5、SortedSet类型操作 4…

第十一章 【后端】商品分类管理微服务(11.2)——Lombok

11.2 Lombok 官网:https://projectlombok.org/ 较新版本的 idea 已默认安装 lombok 插件 Lombok 工具提供一系列的注解,使用这些注解可以不用定义 getter、setter、equals、constructor 等,可以消除 java 代码的臃肿,编译时它会在字节码文件中自动生成这些通用的方法,简…

ElK 8 收集 Nginx 日志

1. 说明 elk 版本&#xff1a;8.15.0 2. 启个 nginx 有 nginx 可以直接使用。我这里是在之前环境下 docker-compose.yml 中启动了个 nginx&#xff1a; nginx:restart: alwaysimage: nginx:1.26.1ports:- "80:80"- "443:443"volumes:#- ./nginx/html:/…

【题解】—— LeetCode一周小结37

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结36 9.合并零之间的节点 题目链接&#xff1a;2181. 合并零之间…

Unity实战案例全解析:PVZ 植物放置分析

前篇&#xff1a;Unity实战案例全解析&#xff1a;PVZ 植物卡片状态分析-CSDN博客 植物应该如何从卡牌状态转为实物&#xff1f; 其实就只需要考虑两个步骤加一个后续处理&#xff1a; 1.点击卡牌后就实例化 需要一个植物状态枚举&#xff0c;因为卡牌分为拿在手上和种植下…

CS61C 2020计算机组成原理Lecture01-数字表示,溢出

1. 原码 原码就是符号化的数值&#xff0c;其编码规则简单直观&#xff1a;正数符号位用0表示&#xff0c;负数符号位用1表示&#xff0c;数值位保持不变。 x0.1101&#xff0c;则[x]原0.1101&#xff1b;x1101&#xff0c;则[x]原01101x -0.1111&#xff0c;则[x]原1.1111&…

Oracle从入门到放弃

Oracle从入门到放弃 左连接和右连接Where子查询单行子查询多行子查询 from子句的子查询select子句的子查询oracle分页序列序列的应用 索引PL/SQL变量声明与赋值select into 赋值变量属性类型 异常循环游标存储函数存储过程不带传出参数的存储过程带传出参数的存储过程 左连接和…

opencv之Canny边缘检测

文章目录 前言1.应用高斯滤波去除图像噪声2.计算梯度3.非极大值抑制4.应用双阈值确定边缘5.Canny函数及使用 前言 Canny边缘检测是一种流行的边缘检测算法&#xff0c;用于检测图像中的边缘。它通过一系列步骤将图像中的像素边缘突出显示出来&#xff0c;主要分为以下几个步骤…

PCL 点云随机渲染颜色

目录 一、概述 1.1原理 1.2实现步骤 1.3 应用场景 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff08;长期更新&#xff09; 一、概述 本文将介绍如何使用PCL库…

uniapp升级Vue3:避坑指南与步骤详解

为什么要升级到 Vue3 Vue3 是 Vue.js 的最新版本&#xff0c;相比 Vue2&#xff0c;它带来了许多改进和新特性&#xff0c;比如更小的包体积、更好的性能、更强大的组合式 API 等。通过升级到 Vue3&#xff0c;我们可以享受到这些新特性带来的好处&#xff0c;提升项目的开发效…

模拟视频推到WVP推流列表

效果 1. wvp创建RTMP 2. 使用ffmpeg将本地的视频转为rtmp ffmpeg -re -i F:rtsp\123.mp4 -c copy -f flv rtmp://192.168.1.237:1935/cd/10001?sign=Z4Y3eYeSg

计算机网络408考研 2022

https://zhuanlan.zhihu.com/p/695446866 1 1 1SDN代表软件定义网络。它是一种网络架构&#xff0c;旨在通过将网络控制平面从数据转发平面分离出来&#xff0c;从而实现网络的灵活性和可编程性。在SDN中&#xff0c;网络管理员可以通过集中式控制器 来动态管理网络流量&…

Google高级应用

网站管理员中心 Google Search Console 谷歌高级搜索&#xff1a;https://www.google.com.hk/advanced_search?hlzh-CN&fg1 基础语法 AND/强迫包含NOT/-除去相关内容~搜索同义词*取代所有字符.取代一个字符" "双引号 强调OR/|或条件搜索()查询分组 高级语…

C#笔记10 Thread类怎么终止(Abort)和阻止(Join)线程

Thread类 C#笔记8 线程是什么&#xff1f;多线程怎么实现和操作&#xff1f;-CSDN博客 C#笔记9 对线程Thread的万字解读 小小多线程直接拿下&#xff01;-CSDN博客 上次说过怎么简单的使用多线程&#xff0c;怎么创建多线程&#xff0c;但是没有具体分析它的终止和释放。 线…

【乐吾乐大屏可视化组态编辑器】使用手册

1 总览 开始设计&#xff1a;大屏可视化设计器 - 乐吾乐Le5le 1.1 画布 画布即绘画区域&#xff0c;将图形拖拽到画布进行编辑&#xff0c;绘制大屏。 1.2 菜单栏 顶部菜单导航&#xff0c;一级菜单可设置Logo、公司名称、文件编辑、常用编辑、查看、帮助&#xff0c;设置大…

text2sql(NL2Sql)综述《The Dawn of Natural Language to SQL: Are We Fully Ready?》

《The Dawn of Natural Language to SQL: Are We Fully Ready?》(github)出自2024年6月的NL2SQL(Natural language to SQL )综述论文。这篇论文尝试回答如下三个问题&#xff1a; 问题1:NL2SQL的现状是什么&#xff1f;(Q1:Where Are we Now?) 论文图1总结了近20年NL2SQL方法…

Cyber Weekly #24

赛博新闻 1、OpenAI发布最强模型o1 本周四&#xff08;9月12日&#xff09;&#xff0c;OpenAI宣布推出OpenAIo1系列模型&#xff0c;标志着AI推理能力的新高度。o1系列包括性能强大的o1以及经济高效的o1-mini&#xff0c;适用于不同复杂度的推理任务。新模型在科学、编码、数…

比亚迪电动汽车的市场占比太惊人

比亚迪&#xff08;BYD&#xff09;在中国电动汽车市场的崛起无疑是近年来最显著的现象之一。凭借其强大的技术整合、丰富的产品线以及价格优势&#xff0c;比亚迪已经迅速成为中国乃至全球电动汽车领域的领导者。在2024年&#xff0c;比亚迪的市场份额在中国汽车市场达到了惊人…

SSHamble:一款针对SSH技术安全的研究与分析工具

关于SSHamble SSHamble是一款功能强大的SSH技术安全分析与研究工具&#xff0c;该工具基于Go语言开发&#xff0c;可以帮助广大研究人员更好地分析SSH相关的安全技术与缺陷问题。 功能介绍 SSHamble 是用于 SSH 实现的研究工具&#xff0c;其中包含下列功能&#xff1a; 1、针…

MySQL练手题--公司和部门平均工资比较(困难)

一、准备工作 Create table If Not Exists Salary (id int, employee_id int, amount int, pay_date date); Create table If Not Exists Employee (employee_id int, department_id int); Truncate table Salary; insert into Salary (id, employee_id, amount, pay_date) va…