算法日记day 41(动归之最长序列问题)

一、最长递增子序列

题目:

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

 

思路:

首先明确dp数组的含义,本题中dp数组含义是在结尾是nums[i]的最长递增子序列的长度为dp[i],由于dp数组由nums数组相照应,因此对dp数组中的所有元素均初始化为1,启用一个判断条件,若后者元素值大于前者,则结果值加一,因此dp数组的递归关系式为

                                              dp[i] = max(dp[j]+1,dp[i])

代码:

public int lengthOfLIS(int[] nums) {
    int n = nums.length; // 获取输入数组的长度
    int[] dp = new int[n]; // 创建一个数组 dp,用于记录以每个元素为结尾的最长递增子序列的长度
    
    // 初始化 dp 数组,每个位置的初始值设为 1
    // 因为每个元素至少可以组成一个长度为 1 的递增子序列(它自己)
    for (int i = 0; i < n; i++) {
        dp[i] = 1;
    }
    
    int result = 1; // 初始化 result,用于记录全局的最长递增子序列的长度

    // 遍历每个元素,确定以每个元素为结尾的最长递增子序列的长度
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < i; j++) {
            // 如果 nums[j] 小于 nums[i],说明 nums[i] 可以接在 nums[j] 后面形成递增子序列
            if (nums[j] < nums[i]) {
                // 更新 dp[i] 为以 nums[i] 结尾的最长递增子序列的长度
                dp[i] = Math.max(dp[j] + 1, dp[i]);
            }
        }
        // 更新 result 为当前最大递增子序列的长度
        result = result > dp[i] ? result : dp[i];
    }

    // 返回全局最长递增子序列的长度
    return result;
}
  • n 是输入数组 nums 的长度。
  • dp 数组用于存储以 nums[i] 为结尾的最长递增子序列的长度。初始化 dp[i] 为 1,表示每个元素自身是一个长度为 1 的递增子序列。
  • 外层循环 i 遍历数组的每一个元素。
  • 内层循环 j 遍历 i 前面的所有元素,检查是否存在比 nums[i] 小的元素。
  • 如果 nums[j] < nums[i],则说明 nums[i] 可以接在 nums[j] 后面形成一个递增子序列,因此更新 dp[i] 为 dp[j] + 1 和 dp[i] 的较大值。
  • 更新 result 为当前的 dp[i] 和 result 的较大值,记录全局的最大递增子序列长度。
  • 最终,result 存储了数组中最长递增子序列的长度,返回这个值。

二、最长连续递增子序列 

题目:

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 l 和 rl < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

示例 1:

输入:nums = [1,3,5,4,7]
输出:3
解释:最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。 

示例 2:

输入:nums = [2,2,2,2,2]
输出:1
解释:最长连续递增序列是 [2], 长度为1。

思路:

dp数组的含义是nums数组中第 i 个元素的最长连续连续子序列的产地古为dp[i],与最长连续子序列不同的是,本题子序列中的元素必须前后连续,因此需要比较的是i+1与i元素的大小,dp数组初始化仍为1,递归关系为

                            

                                            dp[i+1] = dp[i]+1

代码:

public int findLengthOfLCIS(int[] nums) {
    int n = nums.length; // 获取输入数组的长度
    int[] dp = new int[n]; // 创建一个数组 dp,用于记录以每个元素为结尾的最长连续递增子序列的长度
    
    // 初始化 dp 数组,每个位置的初始值设为 1
    // 因为每个元素至少可以组成一个长度为 1 的递增子序列(它自己)
    for (int i = 0; i < dp.length; i++) {
        dp[i] = 1;
    }
    
    int result = 1; // 初始化 result,用于记录全局的最长连续递增子序列的长度

    // 遍历数组中的每一对相邻元素
    for (int i = 0; i < n - 1; i++) {
        // 如果当前元素小于下一个元素,说明可以继续形成递增子序列
        if (nums[i + 1] > nums[i]) {
            dp[i + 1] = dp[i] + 1; // 更新 dp[i + 1],表示以 nums[i + 1] 为结尾的递增子序列长度
        }
        // 更新 result 为当前的 dp[i + 1] 和 result 的较大值
        result = result > dp[i + 1] ? result : dp[i + 1];
    }
    
    // 返回全局最长连续递增子序列的长度
    return result;
}
  • n 是输入数组 nums 的长度。
  • dp 数组用于存储以每个元素为结尾的最长连续递增子序列的长度。初始化 dp[i] 为 1,表示每个元素自身是一个长度为 1 的递增子序列。
  • 遍历数组中的每一对相邻元素,通过 i 和 i + 1 访问。
  • 如果 nums[i + 1] > nums[i],说明 nums[i + 1] 可以继续延续前面的递增子序列,因此更新 dp[i + 1] 为 dp[i] + 1
  • 更新 result 为当前的 dp[i + 1] 和 result 的较大值,以保持全局的最大连续递增子序列长度。
  • 最终,result 变量保存了数组中最长的连续递增子序列的长度,返回这个值。

三、最长重复子数组 

题目:

给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 

示例 1:

输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是 [3,2,1] 。

示例 2:

输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
输出:5

思路:

有关两个数组中比较最长的公共子数组,定义dp数组以i-1为结尾的在nums1数组和以j-1为结尾nums2数组中的最长公共子数组为dp[i][j],不难推出dp数组的递推公式

                                          dp[i][j] = dp[i-1][j-1]+1

代码:

public int findLength(int[] nums1, int[] nums2) {
    // 获取两个数组的长度,并将长度加1,便于创建dp数组(包含边界情况)
    int n1 = nums1.length + 1;
    int n2 = nums2.length + 1;

    // 初始化结果变量为0
    int result = 0;

    // 创建dp二维数组,dp[i][j]表示以nums1[i-1]和nums2[j-1]结尾的最长公共子数组的长度
    int[][] dp = new int[n1][n2];

    // 遍历dp数组填充数据
    for (int i = 1; i < n1; i++) {
        for (int j = 1; j < n2; j++) {
            // 如果nums1的第i-1个元素等于nums2的第j-1个元素
            if (nums1[i - 1] == nums2[j - 1]) {
                // 更新dp[i][j],表示在dp[i-1][j-1]的基础上增加1
                dp[i][j] = dp[i - 1][j - 1] + 1;
            }
            // 更新结果为当前dp[i][j]和已有结果的最大值
            if (dp[i][j] > result) {
                result = dp[i][j];
            }
        }
    }

    // 返回最长公共子数组的长度
    return result;
}
  • n1 和 n2 是 nums1 和 nums2 长度加 1,用于构建 dp 数组(用于存储最长公共子数组的长度)。
  • result 用于保存最终的最长公共子数组的长度。
  • dp 数组的 dp[i][j] 存储 nums1[0..i-1] 和 nums2[0..j-1] 中的最长公共子数组的长度。
  • 通过双重循环遍历 dp 数组。i 和 j 分别代表 nums1 和 nums2 中的索引。
  • 当 nums1[i - 1] 等于 nums2[j - 1] 时,dp[i][j] 表示以 nums1[i - 1] 和 nums2[j - 1] 为结尾的最长公共子数组的长度。
  • 如果 dp[i][j] 大于当前 result,则更新 result
  • 返回最长公共子数组的长度。

四、最长公共子序列 

题目:

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

  • 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:

输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace" ,它的长度为 3 。

示例 2:

输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc" ,它的长度为 3 。

示例 3:

输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0 。

思路:

首先明确dp数组的含义,本题中dp数组为对于长度为[0,i-1]的text1数组和长度为[0,j-1]的text2数组,他们的最长公共子序列为dp[i][j],对于对应元素值相同的情况,dp数组的递推式为

                                                      dp[i][j] = dp[i-1][j-1] + 1

对于相对应元素不相同的情况

例如text1="abc",text2="ace"此时处理起来应该取dp[i][j-1]的长度,即为text1中跳过第二个元素取第三个,或者text2中取前两个元素舍弃第三个

同理如果是text1="ace",text2="abc",此时处理起来应该取dp[i-1][j]的长度,即为text1中取前两个元素,或者text2舍弃第三个跳过第二个元素取第三个,两者之前取最大值,因此递推式为

                                               dp[i][j] = max(dp[i-1][j],dp[i][j-1])

代码:

public int longestCommonSubsequence(String text1, String text2) {
    // 获取两个字符串的长度,并将长度加1,便于创建dp数组(包含边界情况)
    int n1 = text1.length() + 1;
    int n2 = text2.length() + 1;

    // 将字符串转换为字符数组
    char[] c1 = text1.toCharArray();
    char[] c2 = text2.toCharArray();

    // 创建dp二维数组,dp[i][j]表示text1前i个字符和text2前j个字符的最长公共子序列长度
    int[][] dp = new int[n1][n2];

    // 遍历dp数组填充数据
    for (int i = 1; i < n1; i++) {
        for (int j = 1; j < n2; j++) {
            // 如果text1的第i-1个字符等于text2的第j-1个字符
            if (c1[i - 1] == c2[j - 1]) {
                // 更新dp[i][j],表示在dp[i-1][j-1]的基础上增加1
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                // 否则dp[i][j]取dp[i-1][j]和dp[i][j-1]的最大值
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }

    // 返回最长公共子序列的长度
    return dp[text1.length()][text2.length()];
}
  • n1 和 n2 是 text1 和 text2 的长度加 1,用于创建 dp 数组,以包含边界条件。
  • c1 和 c2 是将 text1 和 text2 转换为字符数组。
  • dp 数组的 dp[i][j] 存储 text1[0..i-1] 和 text2[0..j-1] 的最长公共子序列的长度。
  • 遍历 dp 数组。i 和 j 分别表示 text1 和 text2 中的索引。
  • 当 c1[i - 1] 等于 c2[j - 1] 时,dp[i][j] 表示在 dp[i-1][j-1] 的基础上增加 1。
  • 否则,dp[i][j] 取 dp[i-1][j] 和 dp[i][j-1] 的最大值,表示不匹配时最长公共子序列的长度。
  • 返回 dp 数组中最后一个元素,即 text1 和 text2 的最长公共子序列的长度。

今天的学习就到这里 

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值