300.最长递增子序列 674.最长连续递增序列 718.最长重复子数组

300.最长递增子序列 674.最长连续递增序列 718.最长重复子数组

300.最长递增子序列

力扣题目链接(opens new window)

给你一个整数数组 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

提示:

  • 1 <= nums.length <= 2500
  • -10^4 <= nums[i] <= 104

思路

思路:动态规划
题目要求最长递增子序列。子序列这一类型题目,可以使用动态规划作为通解
看题目感觉很简单,找最长递增子序列而已。但越想越难,甚至暴力算法也没有思路

1.dp数组以及下标代表含义
初始想法是定义dp一维数组,i表示当前下标以及之前数组元素之中,最长递增子序列的长度
但这种想法有缺陷。定义dp数组是为递推公式服务,需要找到dp[i] 和 dp[i-2] 甚至有可能是dp[i-3] dp[i-4]之间的关系,然后推导递推公式
假如说nums[i] > nums[i-1]
那么dp[i] 就一定比 dp[i-1]大吗?其实不是的
因为对dp数组的定义为【当前下标以及之前数组元素之中,最长递增子序列的长度】。最长子序列不一定会包含nums[i-1],nums[i]
那么这两个元素的比较没有意义。
怎样才能让dp[i-1] 和 dp[i]的比较有意义?
修改dp数组的定义方式:定义dp一维数组,i表示以nums[i]为结尾的最长递增子序列的长度
2.确定递推公式
以i为起点,向前遍历。若 nums[j] > nums[i]
dp[i] = Math.max(dp[j] + 1,dp[i])
3.dp数组初始化
dp数组初始值为1
4.遍历顺序,dp[i]由dp[i-1]推导,故正序
5.举例推导dp数组
时间复杂度o(n)
空间复杂度o(n)

代码如下

public static void main(String[] args) {
    int[] prices = new int[]{0, 3, 1, 6, 2, 2, 7};
    lengthOfLIS(prices);
}

public static int lengthOfLIS(int[] nums) {
    if (nums == null || nums.length == 0)
        return 0;
    int[] dp = new int[nums.length];
    for (int i = 0; i < nums.length; i++)
        dp[i] = 1;
    int result = 1;

    for (int i = 1; i < nums.length; i++) {
        for (int j = i - 1; j >= 0; j--) {
            if (nums[j] < nums[i]) {
                dp[i] = Math.max(dp[j] + 1, dp[i]);

            }
            if (dp[i] > result)
                result = dp[i];

        }
    }

    return result;
}

问题

二刷时,只能想出dp数组的定义,但对于递推公式的推导还是没有思路
我一直在想如何把dp[i] 和dp[i-1]关联起来,结果关联不上
因为本题要求非连续递增子序列,不能只比较nums[i]与nums[i - 1],而需要比较nums[j]与nums[i] (j是在0到i之间遍历)。

674.最长连续递增序列

力扣题目链接(opens new window)

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

连续递增的子序列 可以由两个下标 l 和 r(l < 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。

提示:

  • 0 <= nums.length <= 10^4
  • -10^9 <= nums[i] <= 10^9

思路

思路:动态规划
题目不难,也可以使用贪心算法解决,甚至空间复杂度更低,为o(1)
但重点要理解此题和【最长上升子序列】的区别
一个是求最长连续子序列,一个是求最长不连续子序列
最长连续子序列:只需要当前状态和前一个状态对比
最长不连续子序列:需要当前状态i和前 i-1个状态对比
时间复杂度:O(n)
空间复杂度:O(n)

代码如下

public int findLengthOfLCIS(int[] nums) {
    if (nums == null || nums.length == 0)
        return 0;
    int[] dp = new int[nums.length];
    for (int i = 0; i < nums.length; i++)
        dp[i] = 1;
    int result = 1;

    for (int i = 1; i < nums.length; i++) {
        if(nums[i] > nums[i-1])
            dp[i] = dp[i-1] + 1;
        if(result < dp[i])
            result = dp[i];
    }

    return result;
}

718.最长重复子数组

力扣题目链接(opens new window)

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例:

输入:

  • A: [1,2,3,2,1]
  • B: [3,2,1,4,7]
  • 输出:3
  • 解释:长度最长的公共子数组是 [3, 2, 1] 。

提示:

  • 1 <= len(A), len(B) <= 1000
  • 0 <= A[i], B[i] < 100

思路

思路:动态规划
动态规划五部曲
1.dp数组含义及其下标
定义二维数组dp[i][j]。
当AB数组元素为nums[i],nums[j]时,公共子数组(结尾为nums[i] 和 nums[j])的最长长度。
2.定义递推公式
if nums[i] == nums[j]
dp[i][j] = dp[i-1][j-1] + 1
3.dp数组初始化
dp[0][0] = 0
4.遍历顺序
从前向后遍历
5.举例推导dp数组
// 时间复杂度o(n^2)
// 空间复杂度o(n)

代码如下

public static void main(String srgs[]) {
    int[] nums1 = new int[]{0, 1, 1, 0, 1, 1, 1, 0, 1, 0};
    int[] nums2 = new int[]{1, 0, 0, 0, 1, 0, 0, 1, 1, 0};
    findLength(nums1, nums2);
}

public static int findLength(int[] nums1, int[] nums2) {
    if (nums1 == null || nums2 == null)
        return 0;

    int[][] dp = new int[nums1.length][nums2.length];
    int result = 0;
    for (int i = 0; i < nums1.length; i++) {
        if (nums1[i] == nums2[0]) {
            dp[i][0] = 1;
            result = 1;
        }
    }
    for (int j = 0; j < nums2.length; j++) {
        if (nums2[j] == nums1[0]) {
            dp[0][j] = 1;
            result = 1;
        }
    }


    for (int i = 1; i < nums1.length; i++) {
        for (int j = 1; j < nums2.length; j++) {
            if (nums1[i] == nums2[j]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
                result = Math.max(result, dp[i][j]);
            }
        }
    }
    return result;
}

优化

优化
二刷时采用另一种dp数组的定义方式。
这种方式可简化初始化dp数组
个人理解这种dp数组的定义方式适用于【编辑距离】一类的题目
【编辑距离】这类题目特点:从两个数组或者字符串中,使用增删改等步骤,找出符合某些特征的结果
注意题目中说的子数组,其实就是连续子序列。我忽略了这一点,以为求非连续子序列,导致递推公式增加nums1[i - 1] != nums2[j - 1]的情况 dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);

代码如下

public static void main(String srgs[]) {
    int[] nums1 = new int[]{0,1,1,1,1};
    int[] nums2 = new int[]{1,0,1,0,1};
    findLength(nums1, nums2);
}
public static int findLength(int[] nums1, int[] nums2) {
    if (nums1 == null || nums2 == null)
        return 0;

    int[][] dp = new int[nums1.length + 1][nums2.length + 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(result, dp[i][j]);
        }
    }
    return result;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值