算法之区间规划

本文详细探讨了动态规划在区间规划问题中的应用,特别是最长上升子序列问题。通过O(N^2)和O(nlogn)两种解决方案的分析,解释了如何寻找最长上升子序列,并讨论了相关应用。此外,还介绍了最长公共子序列和最长回文子串问题的动态规划解法。
摘要由CSDN通过智能技术生成

1.区间动态规划

  • 这类动态规划的特点是求出最值的序列,往往dp[i][j]或者dp[j]中的j表示以j结尾的动态规划,这种题目的代表题目是求最长上升子序列,最长的公共子串,子序列的问题等等。

2.最长上升子序列问题

2.1 数据量比较小O(N^2)

2.1.1 分析

  • 初始化vector<int> dp(n,1);
  • 状态转移方程dp[i]=max(dp[i],dp[j]+1);
  • 核心代码
int lengthOfLIS(vector<int>& nums) {
   
    vector<int> dp(nums.size(),1);
    int res=0;
    for(int i=0;i<nums.size();i++){
   
        for(int j=0;j<i;j++){
   
           if(nums[j]<nums[i]){
   
                dp[i]=max(dp[i],dp[j]+1);
           }
        }
        res=max(res,dp[i]);
    }
    return res;
}

2.1.2 标记路径

  • 如果题目需要求出路径的话,我们可以同时标记路径,例如序列:{7,9,6,10,7,1,3)的pre分别为{0,0,2,1,2,5,5},刚开始pre全部初始化为自己的下标。最大的dp值为3,所以最长子序列长度为3, 末尾的元素在3位置。追踪其路径为:3->pre[3] ,1->pre[1],0->pre[0](停止) ,因此路径为4,2,1,这个为倒序的,因为从后往前找的。

2.2 数据量比较大O(nlogn)

2.2.1 分析

  • 分析:新建一个 low 数组,low[i]表示长度为i的LIS结尾元素的最小值。对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,也就越可能变得更长。因此,我们只需要维护 low 数组,对于每一个a[i],如果a[i]>low[当前最长的LIS长度],直接把a[i]接到当前最长的LIS后面,即low [++当前最长的LIS长度]=a[i]。 那么,怎么维护 low 数组呢?对于每一个a [ i ],如果a [i]能接到 LIS 后面,就接上去;否则,就用a[i] 取更新low数组。具体方法是,在low数组中找到第一个大于等于 a [i]的元素low[j],用a[i]去更新low[j]。如果从头到尾扫一遍low数组的话,时间复杂度仍是O(n^2)。我们注意到 low数组内部一定是非递减的,因此可以二分low数组,找出第一个大于等于a[i]的元素。一次二分low数组的时间复杂度的O(lgn),所以总的时间复杂度是O(nlogn)。
  • 举例1:有以下序列A[ ]={3,1,2,6,4,5,10,7},求LIS长度?首先我们定义一个B[i]来储存可能的排序序列,len为LIS长度。我们依次把A[ i ]有序地放进B[ i ]里。

A[1]=3,把3放进B[1],此时B[1] = 3,此时len = 1,最小末尾是3。
A[2]=1,因为1比3小,所以可以把B[1]中的3替换为1,此时B[1]=1,此时len=1,最小末尾是1。
A[3]=2,2大于1,就把2放进B[2] = 2,此时B[ ]={1,2},len=2。
同理,A[4]=6,把6放进B[3] = 6,B[ ]={1,2,6},len=3。
A[5]=4,4在2和6之间,比6小,可以把B[3]替换为4,B[ ]={1,2,4},len=3。
A[6]=5,B[4]=5,B[ ]={1,2,4,5},len=4。
A[7]=10,B[5]=10,B[ ]={1,2,4,5,10},len=5。
A[8]=7,7在5和10之间,比10小,可以把B[5]替换为7,B[ ]={1,2,4,5,7},len=5。

  • 举例1总结:最终我们得出LIS长度为5,但是,但是B[ ]中的序列并不一定是正确的最长上升子序列。在这个例子中,我们得到的1 2 4 5 7 恰好是正确的最长上升子序列。
  • 举例2:有以下序列A[ ] = {1,4,7,2,5,9,10,3},求LIS长度。

A[1] = 1,把1放进B[1],此时B[1] = 1,B[ ] = {1},len = 1。
A[2] = 4,把4放进B[2],此时B[2] = 4,B[ ] = {1,4},len = 2。
A[3] = 7,把7放进B[3],此时B[3] = 7,B[ ] = {1,4,7},len = 3。
A[4] = 2,因为2比4小,所以把B[2]中的4替换为2,此时B[ ] = {1,2,7},len = 3。
A[5] = 5,因为5比7小,所以把B[3]中的7替换为5,此时B[ ] = {1,2,5},len = 3。
A[6] = 9,把9放进B[4],此时B[4] = 9,B[ ] = {1,2,5,9},len = 4。
A[7] = 10,把10放进B[5],此时B[5] = 10,B[ ] = {1,2,5,9,10},len = 5。
A[8] = 3,因为3比5小,所以把B[3]中的5替换为3,此时B[ ] = {1,2,3,9,10},len = 5。

  • 举例2总结:最终我们得出LIS长度为5。但是,但是这里的1 2 3 9 10很明显并不是正确的最长上升子序列。因此,B序列并不一定表示最长上升子序列,它只表示相应最长子序列长度的排好序的最小序列。这有什么用呢?我们最后一步3替换5并没有增加最长子序列的长度,而这一步的意义,在于记录最小序列,代表了一种最可能性,只是此种算法为计算LIS而进行的一种替换。假如后面还有两个数据12和15,那么B[ ]将继续更新。
  • 总结:因为在B中插入的数据是有序的,不需要移动,只需要替换,所以可以用二分查找插入的位置,那么插入n个数的时间复杂度为〇(logn),这样我们会把这个求LIS长度的算法复杂度降为O(nlogn)。
  • 核心代码:
int lengthOfLIS(vector<int>& nums) {
   
    vector<int> low(1);
    int k=1;
    low[0]=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值