代码随想录:动态规划|子序列问题全集

希望通过这篇文章能看到你的收获和感悟,或许你有更好的理解与建议与我沟通交流,
希望能看到你的留言,即使一句话也非常有意义

300. 最长递增子序列


动态规划6部曲:

1.问题分析与转化 类比背包问题 物品:序列中的元素,背包容量:序列的长度。 求最长子序列,这个物品必须得装,如果不装,不能从当前位置转化到下一个位置。

2.dp含义:dp[i][j] 记录以nums[i]结尾最长递增子序列的长度

3.递推公式: 是找状态的转化过程!如何由上一个推导当前的状态?
对于当前位置元素 nums[i],从i 位置前面的序列(j < i)中找到一个比它小的元素nums[j] ,然后从这个位置添加nums[i] ,之后我们可以更新长度 dp[i] = dp[j] + 1

比如:[1, 4, 2, 3, 4, 6]
遍历过程:[1,6], [1, 4, 6], [2, 6], [2, 3, 6],[2, 3, 4, 6], 保存最长序列的长度dp[5]=4。

4.遍历顺序: 前一个状态到当前状态,自然的从前向后遍历。但内层循环也可以从后向前,为什么?根据递推公式的原理可以找到答案,我们记录的是最大值,跟顺序无关。

5.初始化 全置为1。 递推中求最大值,我们就去找最小值。最小长度递增子序列只包含自己1个元素,长度为1。

6.边界处理与优化 i可以从1开始。dp[0]包含一个元素已经初始化为1了。j < i 从i位置前边找一个数

class Solution {
 public:
  /**
   * @brief dp[i]记录以nums[i]结尾的最长递增的子序列长度(可以不连续但严格递增)
   * 然后返回dp[i]的最大值
   */
  int lengthOfLIS(vector<int>& nums) {
    vector<int> dp(nums.size(), 1);
    // 类比背包问题,外层是容量
    for (int i = 1; i < nums.size(); ++i) {
      // 内层扫描物品
      for (int j = 0; j < i; ++j) {
        // 如果找到比当前物品小的值,递增序列从j位置添加nums[i]元素。长度+1
        if (nums[j] < nums[i]) dp[i] = max(dp[i], dp[j] + 1);
      }
    }
    return *max_element(dp.begin(), dp.end());
  }
};

674. 最长连续递增子序列


思路就是:记录每一段的连续递增序列长度,然后选出最大值。

优化版本

class Solution {
 public:
  int findLengthOfLCIS(vector<int>& nums) {
    int res = 1;
    int sub = 1; ///< 连续递增序列长度
    for (int i = 1; i < nums.size(); ++i) {
      if (nums[i] > nums[i - 1]) {
      	//记录连续递增的长度 并更新最大值
        res = max(res, ++sub);
      } else {
      // 注意不连续的时候,从1开始计算
        sub = 1;
      }
    }
    return res;
  }
};

或者

int _findLengthOfLCIS(vector<int>& nums) {
    int res = 1;
    int sub = 1;
    for (int i = 1; i < nums.size(); ++i) {
      if (nums[i] > nums[i - 1]) {
      // 连续递增序列长度
        sub++;
      } else {
      // 求最大值
        res = max(res, sub);
        sub = 1;
      }
    }
    return res;
  }

718. Maximum Length of Repeated Subarray

**思路:**把两个序列进行逐个元素对比A[1,2,3,2,1],B[3,2,1,4,7]

用双层循环,逐个位置对比,如果元素相等,那么他们就是由上一个dp存储的长度+1。

上一个dp是什么?dp[i-1][j-1]即A上一个元素和B中上一个元素的比较结果。i和j都需要减去1,即A序列要回退一个位置,B序列也要回退。

例子:
比较两个序列A[1,2,3,2,1],B[3,2,1,4,7],
dp[i-1][j-1]就是A[1,2,3,2,1],B[3,2,1,4,7]

看图理解,加深印象
代码随想录:动态规划|子序列问题全集_第1张图片

确定dp含义:dp[i][j] 表示以 nums1[i-1]nums2[j-1] 结尾的最长重复子数组的长度。 注意:dp中索引代表第几个元素,dp表示第i个nums1元素nums1[i-1],第j个nums2元素nums2[j-1].

**递推公式:**如果第i个,第j个相等,那么在之前重复子序列长度+1.if (nums1[i - 1] == nums2[j - 1]) dp[j] = dp[j - 1] + 1;
为什么要-1?为什么不-2? 为什么重复序列必须是连续的。

**初始化:**初始化为0,最小值不相等,长度为0.

为什么dp是i,j 而nums中是i-1,j-1了?
因为便于初始化, dp[0][0]如果代表是第一个元素,那么dp[i-1][j-1]就越界了。如果我们手动把第一行和第一列初始化,代码就显得很繁琐。

**遍历顺序:**从前向后。如果是滚动数组,内层从后向前,避免覆盖历史数据。

二维比较简单

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;
    }
};

滚动数组优化版
注意,元素不相等的时候,需要置0
if (nums1[i - 1] != nums2[j - 1]) dp[i][j]=0;

class Solution {
 public:
  int findLength(vector<int> &nums1, vector<int> &nums2) {
    // 索引从1开始代表第i个元素,空出0位置便于初始化进行递推
    vector<int> dp(nums2.size() + 1, 0);

    // 遍历两个数组
    for (int i = 1; i <= nums1.size(); ++i) {
      // 从后向前遍历,避免覆盖历史记录
      for (int j = nums2.size(); j > 0; --j) {
        // 第i个元素索引为i-1
        if (nums1[i - 1] == nums2[j - 1]) {
          dp[j] = dp[j - 1] + 1;
        } else dp[j] = 0;  ///< 注意滚动数组需要手动置0
      }
    }
    return *max_element(dp.begin(), dp.end());
  }
};

你可能感兴趣的:(动态规划,算法)