代码随想录 | Day 52 - LeetCode 300. 最长递增子序列、LeetCode 674. 最长连续递增序列、LeetCode 718. 最长重复子数组

        今天的三道题都是要将dp[i]定义为“以数组的第i位为子序列末尾的子序列长度”。重点是该序列一定包含数组的第i位,要在此基础上来判断这一结果的来源。并且如果第i位不满足要求的话,就要将dp[1]重新设置为0或1或其他值。


        第1题(LeetCode 300. 最长递增子序列)自己想到了解法。dp数组定义是这道题的关键,将dp[i]定义为“从nums开头到下标i为末尾的部分中,以nums[i]为末尾的最长递增子序列长度”。也就是说,dp[i]对应的子序列中必须包含nums[i]。而这一序列除过nums[i]的上一位置,可能是nums[i]前面的任意一个数字。所以要计算dp[i]的话,就要从0遍历到(i - 1)位置,遇到比nums[i]小的数字nums[j],就将dp[j]加上1,然后取所有dp[j] + 1(j < i)当中的最大值作为dp[i]的结果。所以状态转移方程为dp[i] = max(dp[i], dp[j] + 1)(j < i),只有当nums[j] < nums[i]时才进行这一操作。遍历方向上,外层和内层都是正向遍历。

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> dp(nums.size(), 1);
        dp[0] = 1;
        int ans = dp[0];
        for (int i = 1; i < nums.size(); ++i) {
            for (int j = 0; j < i; ++j) {
                if (nums[j] < nums[i]) {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};

        第2题(LeetCode 674. 最长连续递增序列)比较简单,相比上一题,对递增子序列加上了“连续”的限制。这样一来,dp[i]对应的序列的上一数字,只能是nums[i - 1]。否则,就从nums[i]开始重新记录子序列。所以这一题的dp数组定义仍然沿用上一题的,dp[i]定义为“从nums开头到下标i为末尾的部分中,以nums[i]为末尾的最长连续递增子序列长度”。这一定义只限制了子序列末尾必须为nums[i],而没有限制开头是哪个。

        由于dp[i]只与nums[i - 1]和dp[i - 1]相关,所以不再需要内层循环,只要在外层循环正向遍历,判断前一数字与当前数字的大小关系。如果当前数字更大,就将字序列长度加1;否则,就将子序列长度重新设置为1。

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        vector<int> dp(nums.size());
        dp[0] = 1;
        int ans = dp[0];
        for (int i = 1; i < nums.size(); ++i) {
            if (nums[i] > nums[i - 1]) {
                dp[i] = dp[i - 1] + 1;
            }
            else {
                dp[i] = 1;
            }
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};

        题解中还有贪心解法。思路就是遇到比前一数字更大的数字就将计数cnt加1,否则就将其重新设置为1,并在变力的过程中统计cnt的最大值作为最终结果。

class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        int ans = 1, cnt = 1;
        for (int i = 1; i < nums.size(); ++i) {
            if (nums[i] > nums[i - 1]) {
                cnt++;
            }
            else {
                cnt = 1;
            }
            ans = max(ans, cnt);
        }
        return ans;
    }
};

        第3题(LeetCode 718. 最长重复子数组)也不是很难,自己想到解法。这一题要用到二维dp数组,dp[i][j]定义为“对于nums1的[0, i]部分,nums2的[0, j]部分,两者分别以nums1[i]和nums2[j]结尾的最长重复子数组长度”。当nums1[i]与nums2[j]相等时,这一长度就在dp[i - 1][j - 1]的基础上再增加1;而不相等时,就要重新归零。

        dp每个位置都依赖于其左上角的数字,所以要初始化第0行和第0列。具体的方式就是遇到nums1/nums2某一位置的数字与nums2[0]/nums1[0]想等时,就将该位置的dp值设置为1,其他位置都为0。也正因为每个位置都依赖于其左上角,所以双重循环都正向遍历。在遍历过程中,要不断更新最大值作为最终的结果。

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp(nums1.size(), vector<int>(nums2.size(), 0));
        int ans = 0;
        for (int j = 0; j < nums2.size(); ++j) {
            if (nums2[j] == nums1[0]) {
                dp[0][j] = 1;
                ans = 1;
            }
        }
        for (int i = 1; i < nums1.size(); ++i) {
            if (nums1[i] == nums2[0]) {
                dp[i][0] = 1;
                ans = 1;
            }
        }
        for (int i = 1; i < nums1.size(); ++i) {
            for (int j = 1; j < nums2.size(); ++j) {
                if (nums1[i] == nums2[j]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else {
                    dp[i][j] = 0;
                }
                ans = max(ans, dp[i][j]);
            }
        }
        return ans;
    }
};

        题解在实现上更为简洁,主要区别是在定义上将dp[i][j]对应的位置从nums1[i]和nums2[j],变为了nums1[i - 1]和nums2[j - 1]。这样一来,虽然dp矩阵的大小要设置为(nums1.size() + 1, nums2.size() + 1),但不再需要初始化nums1[0]和nums2[0]对应的第1行和第1列了。只需要将无物理含义的第0行和第0列全部初始化为0,然后从第1行、第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 ans = 0;
        for (int i = 1; i <= nums1.size(); ++i) {
            for (int j = 1; j <= nums2.size(); ++j) {
                if (nums1[i - 1] == nums2[j - 1]) { // 不再是nums1[i] == nums2[j]
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                else {
                    dp[i][j] = 0;
                }
                ans = max(ans, dp[i][j]);
            }
        }
        return ans;
    }
};

        这种实现方法需要注意,在判断两数字是否相等的时候,要将下标从i、j改为(i - 1)、(j - 1)。

        再次由于每个位置的dp值只依赖于其左上方,所以可以用节省空间的方法,将dp矩阵压缩为一维数组来实现。为避免当前行将上一行的值覆盖,导致当前位置右边的数字丢失原本左上角数字,所以内层循环要改为逆向。

class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<int> dp(nums2.size() + 1, 0);
        int ans = 0;
        for (int i = 1; i <= nums1.size(); ++i) {
            for (int j = nums2.size(); j >= 1; --j) {
                if (nums1[i - 1] == nums2[j - 1]) { // 不再是nums1[i] == nums2[j]
                    dp[j] = dp[j - 1] + 1;
                }
                else {
                    dp[j] = 0;
                }
                ans = max(ans, dp[j]);
            }
        }
        return ans;
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值