动态规划专练:力扣第718、1143题

力扣第718题-最长重复子数组

1.本题是一道考察动态规划的经典问题,设置一个二维dpnums1Size + 1nums2Size + 1数组,来记录长度为i的nums1和长度为j的nums2的最长公共子数组长度,元素初始化为0。当nums1i - 1 == nums2j - 1时说明当前元素相同,此时的最长公共子数组长度dpij就等于dpi - 1j - 1 + 1。每次循环都更新当前最长的公共子数组长度res。完整代码如下:

cpp 复制代码
 1. int findLength(int* nums1, int nums1Size, int* nums2, int nums2Size) {
 2.     // dp[i][j]:nums1前i个、nums2前j个元素,以nums1[i-1]、nums2[j-1]结尾的最长公共子数组长度
 3.     int dp[nums1Size + 1][nums2Size + 1];
 4.     // 初始化dp数组全部置0
 5.     for (int i = 0; i <= nums1Size; i++){
 6.         memset(dp[i], 0, sizeof(dp[i]));
 7.     }
 8.  
 9.     int res = 0;
10.     // 遍历nums1每一位
11.     for (int i = 1; i <= nums1Size; i++){
12.         // 遍历nums2每一位
13.         for (int j = 1; j <= nums2Size; j++){
14.             // 当前两数字相等,公共子数组长度 = 左上角dp值 + 1
15.             if (nums1[i - 1] == nums2[j - 1]){
16.                 dp[i][j] = dp[i - 1][j - 1] + 1;
17.             }
18.             // 不相等时dp[i][j]保持0,更新全局最大长度
19.             res = fmax(res, dp[i][j]);
20.         }
21.     }
22.  
23.     return res;
24. }

该算法时间复杂度和空间复杂度均为O(nums1Size * nums2Size)。

2.可以看到递推公式中当前项的dp只和上一层的有关,所以可以将二维dp数组改为一维动态dp数组。需要注意的是此时的内层循环就需要逆序遍历,防止元素被重复计算,同时当nums1i - 1 != nums2j - 1时说明连续子数组在这里断掉了,需要将当前dp值置零。完整代码如下:

cpp 复制代码
 1. int findLength(int* nums1, int nums1Size, int* nums2, int nums2Size) {
 2.     // 一维滚动dp数组,dp[j]表示nums1前i个、nums2前j个以末尾元素结尾的最长公共子数组长度
 3.     int dp[nums2Size + 1];
 4.     // 数组初始化为0
 5.     memset(dp, 0, sizeof(dp));
 6.  
 7.     int res = 0;
 8.     // 遍历nums1每一个元素
 9.     for (int i = 1; i <= nums1Size; i++){
10.         // 倒序遍历nums2,防止dp[j-1]提前被覆盖
11.         for (int j = nums2Size; j >= 1; j--){
12.             if (nums1[i - 1] == nums2[j - 1]){
13.                 // 当前元素匹配,继承左上方dp[j-1]的值并+1
14.                 dp[j] = dp[j - 1] + 1;
15.             } else {
16.                 // 元素不匹配,以当前位置结尾的公共子数组长度归零
17.                 dp[j] = 0;
18.             }
19.             // 更新全局最长公共子数组长度
20.             res = fmax(res, dp[j]);
21.         }
22.     }
23.  
24.     return res;
25. }

该算法时间复杂度为O(nums1Size * nums2Size),空间复杂度为O(nums2Size)。

力扣第1143题-最长公共子序列

1.本题和力扣第718题-最长重复子数组比较相似,区别在于本题的公共子序列不要求连续,这就代表最长公共子序列的值在dp数组中可以继承而不是清零。当text1i - 1 == text2j - 1时递推公式仍为dpij = dpi - 1j - 1 + 1,而不相等时就要比较上方或者左边的较大值来继承(从这两个方向前进一步都可以到达当前位置,所以有两种情况),递推公式为dpij = fmax(dpi - 1j, dpij - 1)。完整代码如下:

cpp 复制代码
 1. int longestCommonSubsequence(char* text1, char* text2) {
 2.     // 获取两个字符串长度
 3.     int len1 = strlen(text1);
 4.     int len2 = strlen(text2);
 5.     // dp[i][j]:text1前i个字符、text2前j个字符的最长公共子序列长度
 6.     int dp[len1 + 1][len2 + 1];
 7.     // 将dp数组全部初始化为0
 8.     for (int i = 0; i <= len1; i++){
 9.         memset(dp[i], 0, sizeof(dp[i]));
10.     }
11.  
12.     // 遍历text1每个字符
13.     for (int i = 1; i <= len1; i++){
14.         // 遍历text2每个字符
15.         for (int j = 1; j <= len2; j++){
16.             if (text1[i - 1] == text2[j - 1]){
17.                 // 字符相等,公共子序列长度等于左上角值+1
18.                 dp[i][j] = dp[i - 1][j - 1] + 1;
19.             } else {
20.                 // 字符不等,取上方或左方较大值
21.                 dp[i][j] = fmax(dp[i - 1][j], dp[i][j - 1]);
22.             }
23.         }
24.     }
25.  
26.     // 两字符串全部字符对应的最长公共子序列结果
27.     return dp[len1][len2];
28. }

该算法时间复杂度和空间复杂度均为O(len1 * len2)。

2.本题也可以使用一维动态dp数组,内层循环由于在字符不等的情况下必须比较同行左边的和上一次当前位置的值,所以dpj - 1需要使用已经更新后的值,必须使用正序遍历。同时为了避免元素被重复使用,需要一个记录之前元素的变量pre和一个记录当前元素的变量cur来辅助(之前都是通过逆序来解决)。完整代码如下:

cpp 复制代码
 1. int longestCommonSubsequence(char* text1, char* text2) {
 2.     int len1 = strlen(text1);
 3.     int len2 = strlen(text2);
 4.     // 一维滚动dp数组,dp[j]代表text1前i个字符、text2前j个字符的LCS长度
 5.     int dp[len2 + 1];
 6.     memset(dp, 0, sizeof(dp));
 7.  
 8.     for (int i = 1; i <= len1; i++){
 9.         // pre保存dp[j-1]更新前的值,等价二维dp[i-1][j-1]
10.         int pre = dp[0];
11.         for (int j = 1; j <= len2; j++){
12.             // 记录更新前的dp[j],作为下一轮j+1的pre
13.             int cur = dp[j];
14.             if (text1[i - 1] == text2[j - 1]){
15.                 // 字符匹配,取左上角pre+1
16.                 dp[j] = pre + 1;
17.             } else {
18.                 // 不匹配,取上方旧dp[j]或左侧新dp[j-1]最大值
19.                 dp[j] = fmax(dp[j], dp[j - 1]);
20.             }
21.             pre = cur;
22.         }
23.     }
24.  
25.     return dp[len2];
26. }

该算法时间复杂度为O(len1 * len2),空间复杂度为O(len2)。

3.遍历方向由状态转移方程中最严苛的依赖限制唯一决定。只要推导分支中存在任何对当前行(新数据,如dpij-1)的依赖,就强制要求正序遍历。在此强制正序的前提下,为解决同时需要上一行旧数据(如dpi-1j-1)造成的读写冲突,不改变遍历方向,而是通过引入标量缓存(即pre变量)进行空间置换。

相关推荐
ChaoZiLL2 小时前
二叉树经典例题
数据结构·算法
不正经学生2 小时前
C语言字符串函数进阶:安全版函数 + 错误处理 + 子串查找
c语言·开发语言·c++·算法·面试
三克的油2 小时前
算法基础-1
数据结构·c++·算法
oort1232 小时前
OortCodex 是奥尔特云 OortCloudSmart 推出的国产化工程化 AI 编码 Agent(奥尔特云编码智能体)
大数据·人工智能·算法
TheBestRucy2 小时前
基于Dify的旅游攻略&王者荣耀攻略智能助手项目
服务器·开发语言·人工智能·python·算法·旅游
你想知道什么?2 小时前
朴素贝叶斯算法-学习笔记
笔记·学习·算法
zlinear数据采集卡2 小时前
ZLinear产品线全景对比:D223 vs DABL7606 vs DABL-G511选型指南
arm开发·嵌入式硬件·算法·fpga开发·开源
数模竞赛Paid answer3 小时前
2026年华东杯数学建模C题收益率预测问题解题全过程文档及程序
算法·数学建模·数据分析·华东杯
依然鸣3 小时前
PTA团体程序设计天梯赛L2真题讲解L2-005-008
开发语言·数据结构·c++·算法·深度优先·pat考试·图论