详详详解动归数组常见习题(C/C++)

2024-05-03 08:04

本文主要是介绍详详详解动归数组常见习题(C/C++),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 最长递增数组序列(必须连续)dp[i] = dp[i - 1] + 1;
    • 最长递归子序列(不需要连续)dp[i] = max(dp[i], dp[j] + 1);俩层循环
    • 总结一维dp
    • 最长重复子数组
    • 最长公共子序列
    • 总结二维dp
    • 最终目标[3692. 最长连续公共子序列 - AcWing题库](https://www.acwing.com/problem/content/3695/)

最长递增数组序列(必须连续)dp[i] = dp[i - 1] + 1;

image-20240410165824281

这个的动态规划比下一个简单一些,但其实大差不差,这个的j不需要遍历i以前的元素,因为我们不求子序列,但是下面的就得求i以前区间的,本题如果不满足 nums[i] > nums[j] 的话 ,那么直接 j=i;更新j指针位置,重新开始计算就好了

卡尔哥完整代码:

image-20240410181226265

其实根本不需要定义 j 变量,直接 i 与 i-1比较就好了~~

最长递归子序列(不需要连续)dp[i] = max(dp[i], dp[j] + 1);俩层循环

用动态规划

10,9,2,5,3,7,101,18

这组数据放在上个题目的答案就是:3 7 101 三个结果

放在这个题目就是:2 5 7 101 四个结果 这就是子序列的区别

dp[i] 表示:最长子序列的长度(以nums[i]结尾的最长递增子序列的长度

image-20240410170858904

我们的 j 是从 i 以前的区间开始寻找,并不是说i前一个+1就是得到的最终值

每一次j在i之前寻找元素的时候,都会出现一个新的 dp[i] ,所以我们最终的 dp[i] 也要从众多 dp[i] 中找出最大的

image-20240410171345552

int lengthOfLIS(int* nums, int numsSize) {int dp[numsSize];for (int i = 0; i < numsSize; i++) {dp[i] = 1; // 初始状态,每个数字自身构成长度为 1 的子序列for (int j = 0; j < i; j++) {if (nums[j] < nums[i]) {dp[i] = fmax(dp[i], dp[j] + 1); // 更新 dp[i]}}}int ans = 0;for (int i = 0; i < numsSize; i++) {ans = fmax(ans, dp[i]); // 寻找 dp 数组中最大的值}return ans;
}

总结一维dp

子序列:i 前面的每一个区间的元素 j都要去遍历

必须连续:dp[i] 只与 i 前一个位置 j 进行判断加法

image-20240410180358826

image-20240410181226265

我的代码写的还是太笨了,完全没必要定义 j 变量嘟!!!

最长重复子数组

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

暴力的话,至少是O(N^3) 复杂度非常高

dp数组的含义: dp [i] [j] i 表示 到了nums1数组的元素下标 j 表示到了nums2数组的元素下标

表示 以i-1为结尾的 nums1 和 j-1为结尾的nums2的最长重复子数组长度 也就是说 我们的 dp[i] [j] 的下标比数组慢一步

(以i结尾,以j结尾这样定义后续会很不方便)

递推公式:if(nums1[i-1]==nums2[j-1])dp [i] [j] =dp[i-1] [j-1] +1; 因为我们是以 i-1 j-1 为结尾的dp数组

初始化:根据我们的定义表示,dp[i] [0] dp[0] [j] 都是没有意义的,所以必须初始化为0;对于其他数字我们也可以初始化为0,因为后续也会覆盖,所以直接全部初始化成0就好了

本题也可以进行状态压缩,为了防止覆盖也是从后往前(担心一个值放俩次),类似于01背包

拓展:为什么 i-1 j-1 为结尾 这样初始化很方便,而且越界问题也随着初始化被解决掉了

A: 1 2 3 2 1

B 0 0 0 0 0 0

3 0 0 0 1 0 0

2 0 0 1 0 2 0

1 0 1 0 0 0 3

4 0 0 0 0 0 0

7 0 0 0 0 0 0

image-20240411103530733

如果dp数组定义为 i结尾 j结尾的 ,那么就是下面这样:我们必须对刚开始第一行也得初始化判断,而且还要担心越界问题

0 1 2 3 2 1

3 0 0 1 0 0

2 0 1 0 2 0

1 1 0 0 0 3

4 0 0 0 0 0

7 0 0 0 0 0

#include<bits/stdc++.h>
using namespace std;
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;//代码循环细节,正常来说我们的size就是元素个数,不是数组边界的大小,那么我们i初始化成1 循环终止条件为 i<=size 这样是会发生越界的 但是由于我们的dp数组定义的是 i-1 j-1 为下标的数组,所以我们必须要到达边界,这样才可以访问到我们的最后一个元素 无论是最长递增 还是最长公共 都是 i<nums1Size 这样子的 说明细节真的很多for(int i=1;i<=nums1.size();i++){for(int j=1;j<=nums2.size();j++){if(nums1[i-1]==nums2[j-1])//这个if条件就忘了写了 光顾着写递推公式 连最基本的条件判断都没写{dp[i][j]=dp[i-1][j-1]+1;}if(dp[i][j] > result) result =dp[i][j];//不需要再遍历一次二维数组,我们通过result顺便保存}}for(size_t i=0;i<=nums1.size();i++){for(size_t j=0;j<=nums2.size();j++){cout << dp[i][j] << " ";}cout << endl;}return result;}
};
int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);Solution s;vector<int>nums1={1,2,3,2,1};vector<int>nums2={3,2,1,4,7};s.findLength(nums2,nums1);return 0;
}

代码中有一个很大的bug:int dp [nums1Size+1] [nums2Size+1]={{0}}; //报错原因 不支持动态开辟二维数组

所以我们还是用vector来进行初始化和运算更方便一些

vector<vector> dp (nums1.size() + 1, vector(nums2.size() + 1, 0));

代码解释:这里运用了构造,nums.size()+1个vector,每一个vector又是nums2.size()+1个0

最长公共子序列

0 a b c d e

0 0 0 0 0 0 dp[0] [j]

a 0 1 1 1 1 1

c 0 1 1 2 2 2

e 0 1 1 2 2 3

image-20240411154221999

dp[i] [j]:表示以 i-1 j-1 的最长公共子序列 一般来说 dp中用来二维的都是定义到 i-1 j-1 其实也很好理解 就像蓝桥杯的扫雷那个题目,我们如果对数组不进行扩充处理的话,就要对第一行和第一列进行特殊情况的判断,这个道理在本题中也是同样的道理

递推公式: if(nums1[i-1]==nums2[j-1])dp [i] [j] =dp[i-1] [j-1] +1;

为什么我们if条件中写的是 i-1 j-1 呢?因为我们是以 i-1 j-1 为结尾的dp数组

if(nums1[i-1] != nums2[j-1])

对于 a b c 与 a c e 此时c 与 e不相同 那么 我们可能考虑 a b c与 a c匹配 :dp[i] [j-1]

对于 a b c 与 a c e 我们也可能考虑 a c e与 a b匹配 :dp[i-1] [j]

这俩种情况都有可能是我们的dp[i] [j]

image-20240411152427568

初始化:我们的第一行第一列全部初始化为0 含义是 空字符与字符匹配结果为0

而且我们遍历的时候是 从左往右 从上往下遍历

而且这个题目不需要去遍历寻找最大值 dp[text1.size()] [text2.size()] 就是我们最终求得的结果 与上题的result不一样

总结二维dp

1.初始化

vector<vector<int> > dp (text1.size()+1,vector<int>(text2.size()+1,0));

刚开始多初始化一行一列,对于解决二维dp有大帮助,省去越界与单独情况特判的麻烦

2.递推公式

if(nums1[i-1]==nums2[j-1])dp [i] [j] =dp[i-1] [j-1] +1;

这其实就是斜对角线

3.代码对比

image-20240411154715253

不同点:

返回值的返回方式不同

递推公式不同,子序列要考虑更多的情况

相同点:

初始化的方式相同

循环控制的终止条件相同

最终目标3692. 最长连续公共子序列 - AcWing题库

最长连续公共子序列 = 最长重复子数组的题目 递推公式只有一个,求的不是子序列而是连续的

将上述代码改吧改吧

#include<bits/stdc++.h>
using namespace std;
class Solution {
public:int findLength(string& nums1, string& 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])//这个if条件就忘了写了 光顾着写递推公式 连最基本的条件判断都没写{dp[i][j]=dp[i-1][j-1]+1;}if(dp[i][j] > result) result =dp[i][j];//不需要再遍历一次二维数组,我们通过result顺便保存}}return result;}
};
int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);Solution s;string s1;string s2;cin >> s1;cin >> s2;cout << s.findLength(s1,s2);return 0;
}

image-20240411184825879

关于有序字符的输出,现在的我还是无法解决…

这篇关于详详详解动归数组常见习题(C/C++)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

嵌入式Linux驱动中的异步通知机制详解

《嵌入式Linux驱动中的异步通知机制详解》:本文主要介绍嵌入式Linux驱动中的异步通知机制,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、异步通知的核心概念1. 什么是异步通知2. 异步通知的关键组件二、异步通知的实现原理三、代码示例分析1. 设备结构

Python中Flask模板的使用与高级技巧详解

《Python中Flask模板的使用与高级技巧详解》在Web开发中,直接将HTML代码写在Python文件中会导致诸多问题,Flask内置了Jinja2模板引擎,完美解决了这些问题,下面我们就来看看F... 目录一、模板渲染基础1.1 为什么需要模板引擎1.2 第一个模板渲染示例1.3 模板渲染原理二、模板

Redis中6种缓存更新策略详解

《Redis中6种缓存更新策略详解》Redis作为一款高性能的内存数据库,已经成为缓存层的首选解决方案,然而,使用缓存时最大的挑战在于保证缓存数据与底层数据源的一致性,本文将介绍Redis中6种缓存更... 目录引言策略一:Cache-Aside(旁路缓存)策略工作原理代码示例优缺点分析适用场景策略二:Re

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

MySQL数据库约束深入详解

《MySQL数据库约束深入详解》:本文主要介绍MySQL数据库约束,在MySQL数据库中,约束是用来限制进入表中的数据类型的一种技术,通过使用约束,可以确保数据的准确性、完整性和可靠性,需要的朋友... 目录一、数据库约束的概念二、约束类型三、NOT NULL 非空约束四、DEFAULT 默认值约束五、UN

Python使用Matplotlib绘制3D曲面图详解

《Python使用Matplotlib绘制3D曲面图详解》:本文主要介绍Python使用Matplotlib绘制3D曲面图,在Python中,使用Matplotlib库绘制3D曲面图可以通过mpl... 目录准备工作绘制简单的 3D 曲面图绘制 3D 曲面图添加线框和透明度控制图形视角Matplotlib

MySQL中的分组和多表连接详解

《MySQL中的分组和多表连接详解》:本文主要介绍MySQL中的分组和多表连接的相关操作,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录mysql中的分组和多表连接一、MySQL的分组(group javascriptby )二、多表连接(表连接会产生大量的数据垃圾)MySQL中的

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

C#如何调用C++库

《C#如何调用C++库》:本文主要介绍C#如何调用C++库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录方法一:使用P/Invoke1. 导出C++函数2. 定义P/Invoke签名3. 调用C++函数方法二:使用C++/CLI作为桥接1. 创建C++/CL