代码随想录算法训练营第43天:动态规划part10:子序列问题

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 。

子序列问题分析:

dp[i]的定义

dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度

为什么一定表示 "以nums[i]结尾的最长递增子序" ,因为我们在 做 递增比较的时候,如果比较 nums[j] 和 nums[i] 的大小,那么两个递增子序列一定分别以nums[j]为结尾 和 nums[i]为结尾, 要不然这个比较就没有意义了,不是尾部元素的比较那么 如何算递增呢。

状态转移方程

位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。

所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值。

dp[i]的初始化

每一个i,对应的dp[i](即最长递增子序列)起始大小至少都是1.

确定遍历顺序

dp[i] 是有0到i-1各个位置的最长递增子序列 推导而来,那么遍历i一定是从前向后遍历。

j其实就是遍历0到i-1,那么是从前到后,还是从后到前遍历都无所谓,只要吧 0 到 i-1 的元素都遍历了就行了。 所以默认习惯 从前向后遍历。

注意**:概括来说:不连续递增子序列的跟前0-i 个状态有关,连续递增的子序列只跟前一个状态有关------这也是考虑如何构建动态规划算法的方法

  • 时间复杂度: O(n^2)
  • 空间复杂度: O(n)
cpp 复制代码
int lengthOfLIS(int* nums, int numsSize) {
   int dp[numsSize];
   dp[0]=1;
   int max_ans=1;//结果不一定在最后一个位置

   for (int i=1;i<numsSize;i++){
        dp[i]=1;//为什么要初始化成1:包含自己必然是最大的子序列,尤其是涉及到fmax,不能随便初始化的
        for (int j=0;j<i;j++){
            if(nums[j]<nums[i]) dp[i]=fmax(dp[i],dp[j]+1);
        }
        printf("%d",dp[i]);
        max_ans=fmax(max_ans, dp[i]);
    }
    return max_ans;
}

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。

可以不用动态规划直接做:贪心 o(n) o(1)复杂度

cpp 复制代码
int findLengthOfLCIS(int* nums, int numsSize) {
    int max_ans=1;
    int this=1;
    for (int i=1;i<numsSize;i++){
        if(nums[i]>nums[i-1]) {
            this++;
            max_ans=fmax(this, max_ans);
        }        
        else {
            this=1;
        }
    }
    return max_ans;
}

感觉动规做法过于复杂:

cpp 复制代码
int findLengthOfLCIS(int* nums, int numsSize) {
    int max_ans=1;
    int dp[numsSize];
    
    dp[0]=1;
    for (int i=1;i<numsSize;i++){
        dp[i]=1;
        if(nums[i]>nums[i-1])  dp[i]=dp[i-1]+1;
     
        max_ans=fmax(max_ans, dp[i]);
    }
    return max_ans;
}

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

分析:

dp【i】【j】:是以i位置、j位置为结尾的匹配的情况------所以只和左上位置元素相关

之前考虑的时候,考虑成i位置、j位置以前的匹配的最大值情况,发现没有办法从上一个状态推导到这个状态------优先直接出结果,如果不能出结果,可以退而求其次,最大值的任务落在max_ans

上,优先考虑本次状态的情况

cpp 复制代码
int findLength(int* nums1, int nums1Size, int* nums2, int nums2Size) {
    int dp[nums1Size][nums2Size];
    memset(dp,0, sizeof(dp));
    if(nums1[0]==nums2[0]) dp[0][0]=1;
    int max_ans=0;

    for (int i=1;i<nums1Size;i++){
        if(nums2[0]==nums1[i]) dp[i][0]=1;
        max_ans=fmax(max_ans, dp[i][0]);
    }

    for (int j=1;j<nums2Size;j++){
        if(nums1[0]==nums2[j]) dp[0][j]=1;
        max_ans=fmax(max_ans, dp[0][j]);
        
    }

    for (int i=1;i<nums1Size;i++){
        for (int j=1;j<nums2Size;j++){
            if(nums1[i]==nums2[j]) dp[i][j]=dp[i-1][j-1]+1;
            max_ans=fmax(max_ans, dp[i][j]);
        }
    }


    return max_ans;
    
}
相关推荐
码流之上31 分钟前
【一看就会一写就废 指间算法】设计电子表格 —— 哈希表、字符串处理
javascript·算法
快手技术2 小时前
快手提出端到端生成式搜索框架 OneSearch,让搜索“一步到位”!
算法
CoovallyAIHub1 天前
中科大DSAI Lab团队多篇论文入选ICCV 2025,推动三维视觉与泛化感知技术突破
深度学习·算法·计算机视觉
NAGNIP1 天前
Serverless 架构下的大模型框架落地实践
算法·架构
moonlifesudo1 天前
半开区间和开区间的两个二分模版
算法
moonlifesudo1 天前
300:最长递增子序列
算法
CoovallyAIHub1 天前
港大&字节重磅发布DanceGRPO:突破视觉生成RLHF瓶颈,多项任务性能提升超180%!
深度学习·算法·计算机视觉
CoovallyAIHub1 天前
英伟达ViPE重磅发布!解决3D感知难题,SLAM+深度学习完美融合(附带数据集下载地址)
深度学习·算法·计算机视觉
聚客AI2 天前
🙋‍♀️Transformer训练与推理全流程:从输入处理到输出生成
人工智能·算法·llm
大怪v2 天前
前端:人工智能?我也会啊!来个花活,😎😎😎“自动驾驶”整起!
前端·javascript·算法