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

 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.hqwc.cn/news/129191.html

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

相关文章

2023-2024年华为ICT网络赛道模拟题库

2023-2024年网络赛道模拟题库上线啦&#xff0c;全面覆盖网络&#xff0c;安全&#xff0c;vlan考点&#xff0c;都是带有解析 参赛对象及要求&#xff1a; 参赛对象&#xff1a;现有华为ICT学院及未来有意愿成为华为ICT学院的本科及高职院校在校学生。 参赛要求&#xff1a…

【Spring MVC研究】MVC如何浏览器请求(service方法)

文章目录 1. DispatcherServlet 的 service 方法1.1. processRequest 方法1.2. doService 方法 背景&#xff1a;平时我们学习 MVC 重点关注的时DispatcherServlet 的 doDispatcher 方法&#xff0c;但是在 doDispatcher 方法之前 还有请求处理的前置过程&#xff0c;这个过程…

electron.js入门-为生产环境构建应用程序

在本章中&#xff0c;我们将学习如何使用可执行文件生成生产应用程序&#xff1b;为此&#xff0c;我们将使用以下软件包&#xff1a; https://www.electron.build/ 需要注意的是&#xff0c;当您有兴趣生成应用程序的可执行文件时&#xff0c;必须在每个Electron.js项目中安装…

linux centos Python + Selenium+Chrome自动化测试环境搭建?

在 CentOS 系统上搭建 Python Selenium Chrome 自动化测试环境&#xff0c;需要执行以下步骤&#xff1a; 1、安装 Python CentOS 7 自带的 Python 版本较老&#xff0c;建议使用 EPEL 库或源码安装 Python 3。例如&#xff0c;使用 EPEL 库安装 Python 3&#xff1a; sud…

Java 线程的生命周期

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

国内就能使用的chatgpt网页版,包含AIGC应用工具

Chatgpt的出现在多个领域带来了重要的影响。它能够显著提高我们的工作效率&#xff0c;无论是编写文案代码还是回答常见问题&#xff0c;都能在短时间内完成任务。通过Chatgpt&#xff0c;我们能够迅速获取所需答案。随着人工智能技术的不断发展&#xff0c;相信在未来AI能够带…

知识图谱1_2——下载neo4j客户端

客户端下载 这里展现一种通过客户端进行操作的方法 https://neo4j.com/download/ 下载desktop客户端 填写完成后开始下载 下载完成后&#xff0c;在命令行输入 chmod x <文件名> #给予文件权限 sudo add-apt-repository universe #安装.appimage所需的包fuse&#x…

3分钟基于Chat GPT完成工作中的小程序

1. 写在前面 GPT自从去年爆发以来&#xff0c;各大公司在大模型方面持续发力&#xff0c;行业大模型也如雨后春笋一般发展迅速&#xff0c;日常工作中比较多的应用场景还是问答模式&#xff0c;作为写程序的辅助也偶尔使用。今天看到一篇翻译的博客“我用 ChatGPT&#xff0c;…

基于正点原子alpha开发板的第三篇系统移植

系统移植的三大步骤如下&#xff1a; 系统uboot移植系统linux移植系统rootfs制作 一言难尽&#xff0c;踩了不少坑&#xff0c;当时只是想学习驱动开发&#xff0c;发现必须要将第三篇系统移植弄好才可以学习后面驱动&#xff0c;现将移植好的文件分享出来&#xff1a; 仓库&…

ViewPager、RecycleView实现轮播图

1.ViewPager实现轮播图形效果。 1&#xff09;layout中&#xff0c;PageIndicatorView轮播的View <RelativeLayoutandroid:layout_width"match_parent"android:layout_height"200dp"android:orientation"vertical"><androidx.viewpager…

Windows安装人大金仓数据库问题解决

一、安装包、授权文件下载 官网下载windows对应的安装包 下载授权文件 二、安装 &#xff08;1&#xff09;将下载的授权文件包解压待用 &#xff08;2&#xff09;将下载好的.iso安装程序解压&#xff0c;使用管理员身份运行安装程序&#xff0c;一路下一步&#xff0c;直…

Android Studio编写xml布局不提示控件的部分属性问题的解决

最近突然发现Android Studio编写xml&#xff0c;发现有一部分控件的属性没有了代码提示&#xff0c;主要体现为id,margin等属性不再有代码提示&#xff0c;如下图。 但是手动输入仍然有效。然后删掉Android Sdk重新回来还是发现有问题&#xff0c;导一个之前的旧项目进来&#…