leetCode 718.最长重复子数组 动态规划 + 优化(滚动数组)

news2024/11/23 12:52:08

 718. 最长重复子数组 - 力扣(LeetCode)

给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 

示例 1:

输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是 [3,2,1] 。

示例 2:

输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
输出:5

 >>思路和分析

  • 暴力破解:先两层for循环确定两个数组起始位置,再来一个for循环或者while,来从两个起始位置开始比较,取得重复子数组的长度
  • 动态规划:用二维数组可以记录两个字符串的所有比较情况,比较好推递推公式

用一个二维的矩阵,也就是二维的dp数组,表示这两个数组比较的所有状态。其实本题相对来说就简单很多了,因为后面的递推公式,遍历顺序,初始化,都比较简单,关键就是在于如何用dp数组去把这两个数组的比较情况,把状态保存出来。这一点是本题的难点所在。

>>动规五部曲

1.确定 dp数组 以及下标含义

  • dp[i][j] : 以下标 i - 1为结尾的 nums1,和以下标 j - 1 为结尾的nums2,最长重复子数组长度为dp[i][j]
  • 注意:以下标 i - 1为结尾的nums1 表明一定是以 nums[i-1]为结尾的字符串

2.确定递推公式

  • 根据dp[i][j]的定义,dp[i][j]的状态只能由 dp[i-1][j-1]推导出来,即当nums1[i-1] 和 nums2[j-1] 相等时,dp[i][j] = dp[i-1][j-1] + 1;
  • 思考(O_O)?:凭啥知道是依据dp[i-1][j-1] 推出 dp[i][j] ,为什么不是dp[i-1][j]或者dp[i][j-1]呢? 
    • 因为这里比较两个数组的元素是否相同,如果相同的话,这两个数组要一起往后退一个格,一起看后面的这个元素的状态,在后面这里一个元素的状态的基础上,再做加一。所以要看dp[i-1][j-1]。也就是说在两个数组中的元素比较完之后,应该一起回退

3.dp数组初始化

根据 dp[i][j] 的定义,可知 dp[i][0]dp[0][j] 都是没有意义的!但 dp[i][0] dp[0][j] 要初始值,为了方便递推公式dp[i][j] = dp[i-1][j-1] + 1;那么可将 dp[i][0] 和 dp[0][j] 初始化为0

4.确定遍历顺序 (这两种遍历方式都可以!)

  • 外层 for 循环遍历 nums1,内层 for 循环遍历 nums2
  • 外层 for 循环遍历 nums2,内层 for 循环遍历 nums1

5.举例推导 dp 数组

class Solution {
public:
    // 动态规划 
    // 时间复杂度:O(n x m),n为nums1长度,m为nums2长度 
    // 空间复杂度:O(n x m)
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
        int result=0;
        for(int i=1;i<=nums1.size();i++) {
            for(int j=1;j<=nums2.size();j++) {
                if(nums1[i-1] == nums2[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
                if (dp[i][j] > result) result = dp[i][j];
            }   
        }
        return result;
    }
};
  • 时间复杂度:O(n x m),n为nums1长度,m为nums2长度 
  • 空间复杂度:O(n x m)

>>优化空间复杂度「滚动数组」

  • 可以看出 dp[i][j] 都是由dp[i-1][j-1]推出,那么压缩成一维数组,也就是dp[j]都是由dp[j-1]推出
  • 相当于可以把上一层dp[i-1][j] 拷贝到下一层 dp[i][j] 来继续用着

注意事项

  • 遍历nums2数组时,要从后向前遍历,可避免重复覆盖
  • 在不满足 nums1[i-1] == nums2[j-1] 时,注意要有赋0的操作

class Solution {
public:
    // 优化 + 滚动数组
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<int> dp(vector<int>(nums2.size()+1,0));
        int result=0;
        for(int i=1;i<=nums1.size();i++) {
            for(int j=nums2.size();j>0;j--) {
                if(nums1[i-1] == nums2[j-1]) dp[j] = dp[j-1] + 1;
                else dp[j]=0;// 注意这里不相等的时候要有赋0的操作
                if (dp[j] > result) result = dp[j];
            }   
        }
        return result;
    }
};
  • 时间复杂度:O(n x m),n为nums1长度,m为nums2长度 
  • 空间复杂度:O(m)

拓展:若我想定义dp[i][j] 是以下标 i 为结尾的nums1,以下标 j 为结尾的 nums2 的最长重复子数组长度,可行不?

可行,只是实现相对麻烦一些。需要将第一行和第一列进行初始化

  • 如果 nums1[i] nums2[0] 相同的话,对应的 dp[i][0] 就要初始为1, 因为此时最长重复子数组为1
  • nums2[j]nums1[0] 相同的话,同理

注意事项:为了让 if (dp[i][j] > result) result = dp[i][j]; 收集到全部结果,两层for训练一定从0开始遍历,这样需要加上 && i > 0 && j > 0 的判断

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp (nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
        int result = 0;

        // 要对第一行,第一列经行初始化
        for (int i = 0; i < nums1.size(); i++) if (nums1[i] == nums2[0]) dp[i][0] = 1;
        for (int j = 0; j < nums2.size(); j++) if (nums1[0] == nums2[j]) dp[0][j] = 1;

        for (int i = 0; i < nums1.size(); i++) {
            for (int j = 0; j < nums2.size(); j++) {
                if (nums1[i] == nums2[j] && i > 0 && j > 0) { // 防止 i-1 出现负数
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                if (dp[i][j] > result) result = dp[i][j];
            }
        }
        return result;
    }
};

总结:我们可以发现方案二其实是在方案一的二维dp数组的上外围和左外围多加了一层0包裹,这样做的好处是可以统一操作,简化代码,也可以更加方便的利用滚动数组进行状态压缩

方案一:
if (nums1[i] == nums2[j] && i > 0 && j > 0) { // 防止 i-1 出现负数
    dp[i][j] = dp[i - 1][j - 1] + 1;
}

方案二:
if(nums1[i-1] == nums2[j-1]) dp[i][j] = dp[i-1][j-1] + 1;

参考和推荐文章、视频:

代码随想录 (programmercarl.com)

动态规划之子序列问题,想清楚DP数组的定义 | LeetCode:718.最长重复子数组_哔哩哔哩_bilibili

来自代码随想录课堂截图:

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

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

相关文章

2023年台州市第三届网络安全技能大赛(MISC)这是神马

这是神马 考点&#xff1a;冰蝎流量特征数据包&#xff0c;需要解密 emoj解密 冰蝎之前做过 特征就是先base64编码在AES编码 我们在数据包里面找到了密钥&#xff1a;144a6b2296333602 这里我们知道了密钥我们就去解密 先筛选HTTP协议 导出HTTP数据流可以看到传了shell.php 随…

美容美甲小程序商城的作用是什么

美容院往往有很高需求&#xff0c;女性悦己经济崛起&#xff0c;加之爱美化程度提升&#xff0c;无论线下环境还是线上互联网信息冲击&#xff0c;美容服务、化妆产品等市场规格一直稳增不减。 通过【雨科】平台制作美容美甲商城&#xff0c;售卖相关服务/产品&#xff0c;模块…

记两次内网入侵溯源

1.1、入侵告警 1、某天深夜主机防护突然爆出CS木马后门&#xff0c;这攻击队不讲武德呀&#xff0c;还好没睡着2、赶紧叫醒旁边看流量设备的哥们儿&#xff0c;尝试Shiro 反序列漏洞攻击成功3、测试目标网站存在shiro反序列化漏洞1.2、上机排查 1、上机将CS木马下载下来&…

Vue3 reactive和ref详解

reactive Vue3.0中的reactive reactive 是 Vue3 中提供的实现响应式数据的方法。在 Vue2 中响应式数据是通过 defineProperty 来实现的&#xff0c;在 Vue3 中响应式数据是通过 ES6 的 Proxy来实现的。reactive 参数必须是对象 (json / arr)如果给 reactive 传递了其它对象 默…

基于JavaWeb的家用电器信息管理系统

本系统采用基于JAVA语言实现、架构模式选择B/S架构&#xff0c;Tomcat7.0及以上作为运行服务器支持&#xff0c;基于JAVA等主要技术和框架设计&#xff0c;idea作为开发环境&#xff0c;数据库采用MYSQL5.7以上。 开发环境&#xff1a; JDK版本&#xff1a;JDK1.8 服务器&…

intel深度相机 D455及D4系列入门教程(逐行代码讲解)

1.介绍 Intel RealSense D435、D455等D4系列&#xff1a; Intel D4系列深度相机是由英特尔&#xff08;Intel&#xff09;公司推出的一款深度感知摄像头&#xff0c;专为实现计算机视觉和深度学习应用而设计。这款相机使用了英特尔的深度感知技术&#xff0c;结合了摄像头和红…

三极管及继电器的使用(单片机如何控制灯泡等大型电器)

1.对于初入硬件的小伙伴一定会用到三极管和继电器&#xff0c;如下图&#xff08;三极管&#xff09; 如下图&#xff08;继电器&#xff09; 当然上述三极管和继电器&#xff0c;只是众多的其中一种&#xff0c;而且继电器是包装好了的&#xff0c;这个应该叫继电器模块&#…

opencv图像的直方图,二维直方图,直方图均衡化

文章目录 opencv图像的直方图&#xff0c;二维直方图&#xff0c;直方图均衡化一、图像的直方图1、什么是图像的直方图&#xff1a;2、直方图的作用&#xff1a;3、如何绘制图像的直方图&#xff1a;&#xff08;1&#xff09;cv::calcHist()函数原型&#xff1a;&#xff08;2…

土地证、工程规划许可证、建设用地规划许可证

土地证、工程规划许可证、建设用地规划许可证办理是房地产开发最基础和初开始的工作流程。 一、土地证是简称&#xff0c;全称为《中华人民共和国国有土地使用证》&#xff0c;发证部门是当地国有土地资源局。 申办单位经当地规划部门审批通过某项目备案审批&#xff0c;在办理…

想要精通算法和SQL的成长之路 - 简化路径

想要精通算法和SQL的成长之路 - 简化路径 前言一. 简化路径 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 简化路径 原题连接 思路如下&#xff1a; 我们根据 "/" 去拆分字符串&#xff0c;得到每个子目录。这里拿到的子目录可能是空字符串&#xff0c;需要…

【Vue2.0源码学习】生命周期篇-销毁阶段(destroy)

文章目录 1. 前言2. 销毁阶段分析3. 总结 1. 前言 接下来到了生命周期流程的最后一个阶段——销毁阶段。从官方文档给出的生命周期流程图中可以看到&#xff0c;当调用了vm.$destroy方法&#xff0c;Vue实例就进入了销毁阶段&#xff0c;该阶段所做的主要工作是将当前的Vue实例…

buuctf PWN warmup_csaw_2016

下载附件&#xff0c;IDA查看 发现直接有显示flag函数 int sub_40060D() {return system("cat flag.txt"); }查看程序起始地址0x40060D ; Attributes: bp-based framesub_40060D proc near ; __unwind { push rbp mov rbp, rsp mov edi, offset comman…

Vue中如何进行音视频录制与视频剪辑

在Vue中进行音视频录制与视频剪辑 随着互联网的发展&#xff0c;音视频处理已经成为前端开发中一个越来越重要的领域。Vue.js作为一款流行的前端框架&#xff0c;为我们提供了丰富的工具和生态系统&#xff0c;使得音视频录制和编辑变得更加容易。本文将介绍如何在Vue中进行音…

SpringMVC修炼之旅(1)什么是SpringMVC

一、什么是MVC 1.1概述 MVC是模型(Model)、视图(View)、控制器(Controller)的简写&#xff0c;是一种软件设计规范。 是将业务逻辑、数据、显示分离的方法来组织代码。 MVC主要作用是降低了视图与业务逻辑间的双向偶合。 MVC不是一种设计模式&#xff0c;MVC是一种架构模式…

[Android]问题解决-Device must be bootloader unlocked

现象 在push文件时&#xff0c;remount命令发生如下报错&#xff1a; $ adb remount Device must be bootloader unlocked解决 1. 打开 开发者模式中的OEM unlocking开关 2. fastboot unlock设置 adb reboot bootloader fastboot flashing unlock根据屏幕提示&#xff0c;…

Python爬取小说(requests和BeautifulSoup)

1.用requests和BeautifulSoup爬取起点中文网小说(https://www.qidian.com/free/all/) 2.选择一篇小说(https://www.qidian.com/book/1037297523/&#xff09; 3.查看小说的卷章和每章对应的章节 4.Chrome浏览器&#xff0c;使用F12&#xff0c;打开开发者模式&#xff0c;查看章…

C超市商品信息查询系统

一、系统界面介绍 1. 超市商品信息查询系统 1、显示商品信息&#xff0c;包括&#xff1a;商品名称、商品种类&#xff08;休闲食品、奶品水饮、生鲜水果&#xff09;、商品价格、商品保质期、商品生产日期&#xff1b; 2、从文件中导入数据、显示、排序、查询的功能。&…

Javascript笔记 rest VS spread

1 rest 2 spread 3 二者区别 在 JavaScript 中&#xff0c;spread 操作符 ... 和 rest 参数都使用三个点 ... 作为前缀&#xff0c;但它们在使用上有一些区别&#xff0c;主要体现在它们的作用和使用场景上。 Spread 操作符 ... 作用&#xff1a; "展开"数组或对象的…

分类预测 | MATLAB实现SSA-CNN麻雀算法优化卷积神经网络数据分类预测

分类预测 | MATLAB实现SSA-CNN麻雀算法优化卷积神经网络数据分类预测 目录 分类预测 | MATLAB实现SSA-CNN麻雀算法优化卷积神经网络数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现SSA-CNN麻雀算法优化卷积神经网络数据分类预测&#xff0c;多特…

2023年中国家用智能微投市场销售概况分析:家用智能微投销量为727万台,销售额为131亿元[图]

随着中国主机游戏市场的开放、电影市场的逐步繁荣&#xff0c;大屏幕带来的体验远高于电视&#xff0c;智能微投设备将成为主机游戏玩家、电影爱好者的选择。2022年&#xff0c;我国家用智能微投销量为727万台&#xff0c;销售额为131亿元&#xff1b;预计2023年家用智能微投行…