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

📅 发布时间:2026/8/14 16:24:50
动态规划专练:力扣第718、1143题
力扣第718题-最长重复子数组1.本题是一道考察动态规划的经典问题设置一个二维dp[nums1Size 1][nums2Size 1]数组来记录长度为i的nums1和长度为j的nums2的最长公共子数组长度元素初始化为0。当nums1[i - 1] nums2[j - 1]时说明当前元素相同此时的最长公共子数组长度dp[i][j]就等于dp[i - 1][j - 1] 1。每次循环都更新当前最长的公共子数组长度res。完整代码如下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数组。需要注意的是此时的内层循环就需要逆序遍历防止元素被重复计算同时当nums1[i - 1] ! nums2[j - 1]时说明连续子数组在这里断掉了需要将当前dp值置零。完整代码如下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数组中可以继承而不是清零。当text1[i - 1] text2[j - 1]时递推公式仍为dp[i][j] dp[i - 1][j - 1] 1而不相等时就要比较上方或者左边的较大值来继承从这两个方向前进一步都可以到达当前位置所以有两种情况递推公式为dp[i][j] fmax(dp[i - 1][j], dp[i][j - 1])。完整代码如下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数组内层循环由于在字符不等的情况下必须比较同行左边的和上一次当前位置的值所以dp[j - 1]需要使用已经更新后的值必须使用正序遍历。同时为了避免元素被重复使用需要一个记录之前元素的变量pre和一个记录当前元素的变量cur来辅助之前都是通过逆序来解决。完整代码如下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]作为下一轮j1的pre 13. int cur dp[j]; 14. if (text1[i - 1] text2[j - 1]){ 15. // 字符匹配取左上角pre1 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.遍历方向由状态转移方程中最严苛的依赖限制唯一决定。只要推导分支中存在任何对当前行新数据如dp[i][j-1]的依赖就强制要求正序遍历。在此强制正序的前提下为解决同时需要上一行旧数据如dp[i-1][j-1]造成的读写冲突不改变遍历方向而是通过引入标量缓存即pre变量进行空间置换。