代码随想录算法训练营第五十二天|300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组

代码随想录算法训练营第五十二天|300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组

最长递增子序列

300.最长递增子序列
文章讲解:https://programmercarl.com/0300.%E6%9C%80%E9%95%BF%E4%B8%8A%E5%8D%87%E5%AD%90%E5%BA%8F%E5%88%97.html
题目链接:https://leetcode.cn/problems/longest-increasing-subsequence/
视频讲解:https://www.bilibili.com/video/BV1ng411J7xP/

自己看到题目的第一想法

没想到好的处理方式。

看完代码随想录之后的想法

列出一张图,如 10 9 2 5 3 7 21这个序列要求最长递增子序列。
代码随想录算法训练营第五十二天|300.最长递增子序列、674. 最长连续递增序列、718. 最长重复子数组_第1张图片

  • 确定dp数组以及其下标含义
    • dp[i]:以nums[i]为结尾的最长连续子序列的长度。
  • 确定递推公式
    • 简单来说就是定义一个i和j,j从0遍历到i-1,然后dp[i]的最大值由j遍历从0到i-1的dp[j]最大值得到,如果nums[i] > nums[j],那dp[i]的最大值就是此刻遍历到的j的dp[j]+1。
  • 确定初始化值
    • 因为每个位置至少有一个自己的数字作为最大值,因此所有的dp[i]的初始值都为1
  • 确定遍历顺序
    • 因为大的值都是由前面小的值推导过来的,因此遍历顺序应该是从前到后
  • 打印数组

自己实现过程中遇到哪些困难

这道题还是相当难理解,后面手写推导了一次状态转移的逻辑就懂了。

public int lengthOfLIS(int[] nums) {
   int[] dp = new int[nums.length];
   Arrays.fill(dp,1);
   int result = dp[0];
   for(int i = 1; i < nums.length; i++){
       for(int j = 0; j < i; j++){
           if(nums[i] > nums[j]){
              dp[i] = Math.max(dp[i], dp[j] + 1);
           }
       }
       result = Math.max(result,dp[i]);
   }
   return result;
}

最长连续递增序列

674. 最长连续递增序列 
文章讲解:https://programmercarl.com/0674.%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E9%80%92%E5%A2%9E%E5%BA%8F%E5%88%97.html
题目链接:https://leetcode.cn/problems/longest-continuous-increasing-subsequence/
视频讲解:https://www.bilibili.com/video/BV1bD4y1778v/

自己看到题目的第一想法

该题和上一题的区别就是本题是连续的。上一题不需要连续。连续和不连续的最大区别就是i只和小一位的j比较,如果比较失败直接为1。
自己写的代码,写完为了验证,还手动推导了一下:

public int findLengthOfLCIS(int[] nums) {
    int[] dp = new int[nums.length];
    Arrays.fill(dp,1);
    int result = dp[0];
    for(int i = 1; i < nums.length; i++){
        if(nums[i] > nums[i - 1]){
            dp[i] = dp[i - 1] + 1;
        }
        result = Math.max(result,dp[i]);
    }
    return result;
 }

看完代码随想录之后的想法

这里和我想的一样,只需要比较nums[i]与nums[i - 1],而不用去比较nums[j]与nums[i] (j是在0到i之间遍历)。
既然不用j了,那么也不用两层for循环,一层for循环就行,比较nums[i] 和 nums[i - 1]。

自己实现过程中遇到哪些困难

最长重复子数组

718. 最长重复子数组
文章讲解:https://programmercarl.com/0718.%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.html
题目链接:https://leetcode.cn/problems/maximum-length-of-repeated-subarray/
视频讲解:https://www.bilibili.com/video/BV178411H7hV/

自己看到题目的第一想法

暴力解法:
两层循环,外层是长度长一点的数组。然后循环遍历nums1,从nums2找到匹配的位置,匹配到了后一起开始向后移动,得到最大值。

动态规划,核心找到递推公式,动态规划五步骤:
dp数组定义:dp[i][j] i表示nums1[i]之前,j表示nums2[j]之前长度最长的子数组。dp[nums1.length -1][nums2.length - 1]就为子数组最长。
递推公式:动态规划没想出来

看完代码随想录之后的想法

动态规划数组定义:

  • 和自己想的类似,dp[i][j]表示以i-1为尾的nums1和以j-1为尾的num2的最长重复子数组的长度。
  • 递推公式if(nums1[i - 1] == nums2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
  • 初始化值,如果这里用dp[i][j]用以i结尾的nums1和以j为尾的nums2的最长重复子数组的话就需要做两次遍历取dp[0][j],dp[i][0]。而这里用i-1,j-1则免去了计算初始值的流程,因为当i=0、j=0时,i-1,j-1无意义,所以直接初始化成0

自己实现过程中遇到哪些困难

dp数组的初始化大小以及循环遍历的次数没处理好,循环的终止条件应该为i <= nums1.length、j <= nums2.length,因为第0位是无意义的值,且比较的是**nums1[i - 1] == nums2[j - 1]**实际要取到nums1.length的位置才能遍历完所有的值。

public int findLength(int[] nums1, int[] nums2) {
    int[][] dp = new int[nums1.length + 1][nums2.length + 1];
    // dp数组定义 dp[i][j]:用nums1的i-1位置为结尾以及nums2的j-1为结尾的最长子数组长度
    int result = 0;
    for(int i = 1; i <= nums1.length; i++){
        for(int j = 1; j <= nums2.length; j++){
            // 推导公式,如果每个数字的位数都相等,则长度加一
            if(nums1[i - 1] == nums2[j - 1]){
                dp[i][j] = dp[i - 1][j - 1] + 1;
                result = Math.max(dp[i][j],result);
            }
        }
    }
    return result;
}

今日收获&学习时长

子序列问题这三题的关键是确定dp数组的定义,这个非常关键。
第一题最长递增子序列,dp数组的定义使用以nums[i]为结尾的最长连续子序列的长度来表示。然后再用j来表示0-i-1的最长子序列,若nums[i] > nums[j],则dp[i] = dp[j] + 1;
第二题最长连续递增子序列和第一题差不多,但是不需要做2次循环,只需要做一次循环然后和i-1比较即可。
第三题最长重复子数组,这道题的核心也是确定dp数组定义,和上面题目差不多,都是用取nums[i],取nums[j]为结尾做dp数组定义。然后再根据题目要义,连续子数组来做匹配。

整体耗时较长,但是都理解了题目。后续二刷

你可能感兴趣的:(算法)