代码随想录算法训练营DAY50|C++动态规划Part11|300.最长递增子序列、674.最长连续递增序列、718.最长重复子数组

本文主要是介绍代码随想录算法训练营DAY50|C++动态规划Part11|300.最长递增子序列、674.最长连续递增序列、718.最长重复子数组,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 300.最长递增子序列
    • 思路
    • CPP代码
  • 674.最长连续递增序列
    • 思路
    • CPP代码
  • 718.最长重复子数组
    • 思路
    • CPP代码

300.最长递增子序列

力扣题目链接

文章讲解:300.最长递增子序列

视频链接:动态规划之子序列问题,元素不连续!| LeetCode:300.最长递增子序列

可以删除或不删除某些元素,保证数组原有的顺序,然后求最长的递增子序列。

这是典型的子序列系列,也是卡哥安排的第一个动规子序列问题。

思路

  • dp[i]的定义

dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度

  • 递推公式

如果j < i ,并且有nums[j] < nums[i],其中,以nums[j]结尾的最长递增子序列长度为dp[j]。以nums[i]结尾的最长递增子序列长度为dp[i]

我们应该有dp[i]=dp[j] + 1,再者,我们会遍历每一个小于i的下标j(也就是说我们会固定i,去遍历j),所以,我们的递推公式是:

dp[i] = max(dp[i], dp[j] + 1)

  • dp数组的初始化

每一个i,对应的dp[i](即最长递增子序列)起始大小至少都是1.

  • 确定遍历顺序

老样子,从前到后遍历,至于j的遍历范围是~i-1,但是遍历方向都无所谓.

for (int i = 1; i < nums.size(); i++) {for (int j = 0; j < i; j++) {if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);}if (dp[i] > result) result = dp[i]; // 取长的子序列
}

这里为什么要定义一个result呢,因为我们如果不保存结果的话,后面还得去遍历dp数组找最大,着实没必要

  • 举例推导dp数组

输入:[0,1,0,3,2],dp数组的变化如下:

CPP代码

class Solution {
public:int lengthOfLIS(vector<int>& nums) {if (nums.size() <= 1) return nums.size();vector<int> dp(nums.size(), 1);int result = 0;for (int i = 1; i < nums.size(); i++) {for (int j = 0; j < i; j++) {if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);}if (dp[i] > result) result = dp[i]; // 取长的子序列}return result;}
};

674.最长连续递增序列

力扣题目链接

文章讲解:674.最长连续递增序列

视频讲解:动态规划之子序列问题,重点在于连续!| LeetCode:674.最长连续递增序列

状态:连续递增子序列和递增子序列区别在哪里?体现在代码中的话又在哪里呢?

来了,本题要求是连续序列,而不是原始序列派生的子序列

思路

  • 明确dp数组的含义

以下标i为结尾的最长连续递增子序列的长度为dp[i]

  • 确定递推公式

在300.最长递增子序列中,我们的dp[i]是由i面前的某个元素j来进行推导。

本题中我们求的是连续的递增子序列,所以我们没有必要去比较前面的所有元素了,在上一题中,我们可是遍历了0~i-1j

所以如果我们nums[i] > nums[i-1],我们就做对应的那个dp[i] + 1的操作,即:

dp[i]=dp[i-1]+1

  • dp数组的初始化

以下标i为结尾的连续递增的子序列长度最少也应该是1,即就是nums[i]这一个元素。

所以dp[i]应该初始1;

  • 确定遍历顺序

从递推公式上可以看出, dp[i + 1]依赖dp[i],所以一定是从前向后遍历。

for (int i = 1; i < nums.size(); i++) {if (nums[i] > nums[i - 1]) { // 连续记录dp[i] = dp[i - 1] + 1;}
}
  • 举例推导dp数组

已输入nums = [1,3,5,4,7]为例,dp数组状态如下:

CPP代码

class Solution {
public:int findLengthOfLCIS(vector<int>& nums) {if (nums.size() == 0) return 0;int result = 1;vector<int> dp(nums.size() ,1);for (int i = 1; i < nums.size(); i++) {if (nums[i] > nums[i - 1]) { // 连续记录dp[i] = dp[i - 1] + 1;}if (dp[i] > result) result = dp[i];}return result;}
};

718.最长重复子数组

力扣题目链接

文章讲解:718.最长重复子数组

视频讲解:动态规划之子序列问题,想清楚DP数组的定义 | LeetCode:718.最长重复子数组

本题要操作两个数组了,就是要求两个数组中最长的重复子数组长度。

这里的子数组呢其实就是连续子序列,强调的是连续

暴力解法:两层for循环确定两个数组起始位置,然后再来一个循环可以是for或者while,来从两个起始位置开始比较,取得重复子数组的长度。

思路

  • dp数组含义

dp[i][j] :以下标i - 1为结尾的num1,和以下标j - 1为结尾的num2,最长重复子数组长度为dp[i][j]

为什么要定义成i-1结尾和以j-1结尾呢?

其实是为了让后续代码尽可能简洁。后续的话会写如果定义成i结尾和j结尾的代码麻烦之处

  • 递推公式

根据dp[i][j]的定义,dp[i][j]的状态只能由dp[i - 1][j - 1]推导出来。

即当nums[i - 1]nums2[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1

根据递推公式可以看出,遍历i 和 j 要从1开始!

if (nums1[i - 1] == nums2[j - 1]) {dp[i][j] = dp[i - 1][j - 1] + 1;
  • 初始化

为了使递推公式能够完成推导,dp[i][0] dp[0][j]初始化为0。

举个例子nums1[0]如果和nums2[0]相同的话,dp[1][1] = dp[0][0] + 1,只有dp[0][0]初始为0,正好符合递推公式逐步累加起来。

  • 遍历顺序

从小到大遍历即可,先遍历哪个也都是可以的,并且在遍历的过程中记录dp[i][j]的最大值

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];}
}
  • 打印

拿nums1: [1,2,3,2,1],nums2: [3,2,1,4,7]为例,画一个dp数组的状态变化,如下:

CPP代码

// 版本一
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 = 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;}
};//滚动数组,遍历nums2的时候,要从后向前遍历,避免重复覆盖
// 版本二
class Solution {
public:int findLength(vector<int>& A, vector<int>& B) {vector<int> dp(vector<int>(B.size() + 1, 0));int result = 0;for (int i = 1; i <= A.size(); i++) {for (int j = B.size(); j > 0; j--) {if (A[i - 1] == B[j - 1]) {dp[j] = dp[j - 1] + 1;} else dp[j] = 0; // 注意这里不相等的时候要有赋0的操作if (dp[j] > result) result = dp[j];}}return result;}
};

这篇关于代码随想录算法训练营DAY50|C++动态规划Part11|300.最长递增子序列、674.最长连续递增序列、718.最长重复子数组的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/957017

相关文章

C++中全局变量和局部变量的区别

《C++中全局变量和局部变量的区别》本文主要介绍了C++中全局变量和局部变量的区别,全局变量和局部变量在作用域和生命周期上有显著的区别,下面就来介绍一下,感兴趣的可以了解一下... 目录一、全局变量定义生命周期存储位置代码示例输出二、局部变量定义生命周期存储位置代码示例输出三、全局变量和局部变量的区别作用域

C++中assign函数的使用

《C++中assign函数的使用》在C++标准模板库中,std::list等容器都提供了assign成员函数,它比操作符更灵活,支持多种初始化方式,下面就来介绍一下assign的用法,具有一定的参考价... 目录​1.assign的基本功能​​语法​2. 具体用法示例​​​(1) 填充n个相同值​​(2)

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Java中的数组与集合基本用法详解

《Java中的数组与集合基本用法详解》本文介绍了Java数组和集合框架的基础知识,数组部分涵盖了一维、二维及多维数组的声明、初始化、访问与遍历方法,以及Arrays类的常用操作,对Java数组与集合相... 目录一、Java数组基础1.1 数组结构概述1.2 一维数组1.2.1 声明与初始化1.2.2 访问

C++中NULL与nullptr的区别小结

《C++中NULL与nullptr的区别小结》本文介绍了C++编程中NULL与nullptr的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编... 目录C++98空值——NULLC++11空值——nullptr区别对比示例 C++98空值——NUL

C++ Log4cpp跨平台日志库的使用小结

《C++Log4cpp跨平台日志库的使用小结》Log4cpp是c++类库,本文详细介绍了C++日志库log4cpp的使用方法,及设置日志输出格式和优先级,具有一定的参考价值,感兴趣的可以了解一下... 目录一、介绍1. log4cpp的日志方式2.设置日志输出的格式3. 设置日志的输出优先级二、Window

一文详解SpringBoot中控制器的动态注册与卸载

《一文详解SpringBoot中控制器的动态注册与卸载》在项目开发中,通过动态注册和卸载控制器功能,可以根据业务场景和项目需要实现功能的动态增加、删除,提高系统的灵活性和可扩展性,下面我们就来看看Sp... 目录项目结构1. 创建 Spring Boot 启动类2. 创建一个测试控制器3. 创建动态控制器注

MySQL查询JSON数组字段包含特定字符串的方法

《MySQL查询JSON数组字段包含特定字符串的方法》在MySQL数据库中,当某个字段存储的是JSON数组,需要查询数组中包含特定字符串的记录时传统的LIKE语句无法直接使用,下面小编就为大家介绍两种... 目录问题背景解决方案对比1. 精确匹配方案(推荐)2. 模糊匹配方案参数化查询示例使用场景建议性能优

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

Java中的雪花算法Snowflake解析与实践技巧

《Java中的雪花算法Snowflake解析与实践技巧》本文解析了雪花算法的原理、Java实现及生产实践,涵盖ID结构、位运算技巧、时钟回拨处理、WorkerId分配等关键点,并探讨了百度UidGen... 目录一、雪花算法核心原理1.1 算法起源1.2 ID结构详解1.3 核心特性二、Java实现解析2.