LeetCode_C++线性动态规划(线性dp)_单串LIS(最长递增子序列,最长递增子序列的个数)

本文详细介绍了线性动态规划的概念,以最长递增子序列为例,解析了动态规划问题的求解步骤和状态转移方程。通过两个LeetCode题目,展示了如何找出一个数列的最长递增子序列以及其个数。状态定义、BaseCase、状态转移函数和状态更新策略是解决问题的关键。
摘要由CSDN通过智能技术生成

1.线性动态规划

线性动态规划的特点是:状态推导按照i从小到大进行推导。
大规模的解答依靠小规模的解答。

解决动态规划问题大体上分为:

  • 问题的Base Case情况(最简单的情况)
  • 问题的状态有哪些
  • 什么选择问题的状态可能会改变
  • 如何定义dp数组表达状态和选择(状态转移)

线性动态规划状态定义一般为:

dp[n]:0~n符合题干的答案

状态转移一般有两种情况:

1. 依赖比 i 小的 O(1) 个子问题

dp[n] = f(dp[n-1], ..., dp[0]);
其中f一般为最大值和最小值函数。具体情况具体分析

2. 依赖比 i 小的 O(n) 个子问题
此时dp[n] 与此前的更小规模的所有子问题 dp[n - 1], dp[n - 2], …, dp[1] 都可能有关系

for(int i=1;i<n;i++){
	for(int j=1;j<i-1;j++){
		dp[i]=f(dp[i],f(dp[j]))
	}
}
其中f一般为最大值和最小值函数。具体情况具体分析

线性动态规划分为:单串,双串,矩阵问题等,这里先练习单串最经典的LIS问题。

2.例题练习

最长递增子序列

LeetCode原题链接
在这里插入图片描述
思路:输入是一个单串,首先思考单串问题中设计状态

  1. dp[i]数组定义:代表数组下标为i时最长子序列。包括数组下标为i的元素。
  2. 初始状态:当i=0时,数组只有一个元素,所以dp[0]=1。
  3. 每次选择是将数组的下标+1,代表检测过这个元素了。这个选择会影响最长子序列。
  4. 状态转化:因为dp[i]是数组下标为i时最长子序列,但不一定dp[i-1]一定小于dp[i]。所以这个状态转化为dp[i]=max(dp[i],(dp[0]…dp[i-1])+1)。
  5. 需要注意,更新dp数组的时机,当num[0]…num[i-1]之间有元素<num[i]才需要更新dp[i]。
  6. 最后的值是dp数组中最大的值,所以需要记录最大值作为返回值。

eg:
nums[j]<nums[i](0<= j <=i-1)
此时的dp[i]=max(dp[i],dp[j]+1);
需要遍历0~i-1次dp数组才可以判断dp[i]的值。
这个状态转移是第二类。依赖比 i 小的 O(n) 个子问题

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        //dp[i]代表数组下标为i时最长子序列
        vector<int>dp(nums.size(),1);
        dp[0]=1;
        int ret=1;
        for(int i=1;i<nums.size();i++){
            for(int j=0;j<i;j++){
                //因为dp[i]最大值不一定是最后值,所以每次都需要遍历一遍dp数组找最大值
                if(nums[j]<nums[i]){
                    dp[i]=max(dp[j]+1,dp[i]);
                }
            }
            ret=max(ret,dp[i]);
        }
        return ret;
    }
};

最长递增子序列的个数

LeetCode原题链接
在这里插入图片描述
这个题的思路与上题相同,但是需要返回最长递增的子序列个数。

首先,我们是按照递增的方式找的子序列,所以数列一定是递增的。

我们定义一个数组count,count[i]代表nums[i]元素之前的递增序列长度有多少个。
最后答案为count数组中长度是最长子序列的所有数组元素和。

int longest;//最长递增子序列
int ans;//最后答案
for(int i=0;i<count.size();i++){
	if (dp[i] == longest) {
    	ans += counts[i];
    }
}

之所以这么设,是因为最长递增子序列可能在dp数组的任何一个位置。

其次在一问中我们可以知道

当dp[i]>dp[j]需要更新dp数组
dp[i]=max(dp[i],(dp[0]…dp[i-1])+1)

for(int i=1;i<nums.size();i++){
    for(int j=0;j<i;j++){
        //因为dp[i]最大值不一定是最后值,所以每次都需要遍历一遍dp数组找最大值
        if(nums[j]<nums[i]){
           //dp[i]=max(dp[j]+1,dp[i]);
           if(dp[j]>=dp[i]){
          	  dp[i]=dp[j]+1	;
          	  count[i]=count[j];//最长递增子序列发生改变,此时count[i]为最长递增子序列,其值和发生改变的上一个位置相同。
           }
           else if(dp[j] + 1 == dp[i]){
           	  //最长递增子序列长度没变化,但是序列发生变化,有count[j] 个额外的序列
           	  count[i]+=count[j];
           }
        }
    }
    ret=max(ret,dp[i]);
}
  • 如果这些序列比 length[i] 长,那么我们就知道我们有count[j] 个长度为 length 的序列。
  • length[j] + 1 = length[i]说明在某个最长递增序列中,num[j]正好是num[i]的前一个元素。那么num[j]所对应的count[j]应该要累加到num[i]所对应的count[i]上
class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        int size=nums.size();
        if(size==0||size==1){
            return size;
        }
        vector<int>dp(size,1);
        vector<int>count(size,1);
        dp[0]=1;
        count[0]=1;
        int maxlen=1;
        for(int i=0;i<size;i++){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]){
                    //dp[i]=max(dp[j]+1,dp[i]);
                    if(dp[j]+1==dp[i]){
                        count[i]+=count[j];
                    }
                    else if(dp[j]+1>=dp[i]){
                        count[i]=count[j];
                        dp[i]=dp[j]+1;                        
                    }
                }
            }
            maxlen=max(maxlen,dp[i]);
        }
        int ans=0;
        for(int i=0;i<count.size();i++){
            if(dp[i]==maxlen){
                ans+=count[i];
            }
        }
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NUC_Dodamce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值