zoukankan      html  css  js  c++  java
  • 最长递增子序列和网易去除最少使从左向右递增又递减问题

    (1)最长递增子序列问题

    有两种方法:(1)动态规划方法(2)类似二分查找的方法O(nlogn)

    动态规划方法:

    以i结尾的序列的最长递增子序列和其[0, i - 1]“前缀”的最长递增子序列有关,设LIS[i]保存以i结尾的最长递增子序列的长度:
        若i = 0,则LIS[i] = 1;
        若i > 0,则LIS[i]的值和其[0, i - 1]前缀的最长递增子序列长度有关,用j遍历[0, i - 1]得到其最长递增子序列为LIS[j],对每一个LIS[j],如果序列array[j]  < array[i]并且LIS[j] + 1 > LIS[i],则LIS[i]的值变成LIS[j] + 1。即:
        LIS[i] = max{1, LIS[j] + 1},其中array[i] > array[j] 且 j = [0, i - 1]。

    代码如下:

    。。。。。晚上补充上

    (2)采用类似二分查找方法

    假设存在一个序列d[1...9] = 2 1 5 3 6 4 8 9 7,可以看出它的LIS长度是5。
        下面一步一步试着找到它。
        我们定义一个序列B,然后令i = 1 to 9逐个考察这个序列。
        此外,我们用一个变量len来记录现在的最长算到多少。
        首先,把d[1]有序的放到B中,令B[1] = 2,就是说当只有一个数字2的时候,长度为1的LIS的最小末尾是2,这时len = 1;
        然后,把d[2]有序的放到B中,令B[1] = 1,就是说长度为1的LIS的最小末尾是1,d[1] = 2已经没用了,很容易理解吧,这时len = 1;
        接着,d[3] = 5,d[3] > B[1],所以令B[1 + 1] = B[2] = d[3] = 5,就是说长度为2的LIS的最小末尾是5,很容易理解吧,这时B[1...2] = 1, 5,len = 2;
        再来,d[4] = 3,它正好在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时B[1...2] = 1,3,len = 2;
        继续,d[5] = 6,它在3的后面,因为B[2] = 3,而6在3后面,于是很容易推知B[3] = 6,这时B[1...3] = 1,3,6,还是很容易理解吧?这时len = 3;
        第6个,d[6] = 4,你看它在3和6之间,于是就可以把6替换掉,得到B[3] = 4。B[1...3] = 1,3,4,这时len = 3;
        第7个,d[7] = 8,它很大,比4大,于是B[4] = 8,这时len = 4;
        第8个,d[8] = 9,得到B[5] = 9,len继续增大,这时len = 5;
        最后一个,d[9] = 7,它在B[3] = 4和B[4] = 8之间,所以我们知道,最新的B[4] = 7, B[1...5] = 1,3,4,7,9,len = 5。
        于是我们知道了LIS的长度为5。
        注意,注意。这个1,3,4,7,9不是LIS,它只是存储了对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。虽然最后一个 d[9] = 7更新进去对于这个数组数据没有什么意义,但是如果后面再出现两个数字8和9,那么就可以把8更新到d[5],9更新到d[6],得到LIS的长度为6。
        然后应该发现一件事情了:在B中插入数据是有序的,而且进行替换而不需要移动——也就是说,可以使用二分查找,将每一个数字的插入时间优化到O(logn),于是算法的时间复杂度就降低到了O(nlogn)了。

    代码如下:

     1 int findlis(int *A,int n,int *lefttoright)     //从左向右最长递增子序列  
     2 {
     3     if(A==NULL||n<0)
     4         return -1;
     5     int *lis=new int[n];
     6     //int *lefttoright=new int[n];
     7     lefttoright[0]=1;                    //lefttoright[i]保存从左到右,以i为终点的最长递增子序列长度,注意已经是正常的长度了,不是小一了
     8     int max=0;                           //max是lis[]的最大下标如lis[]={1,2,4}时,max=2;
     9     lis[0]=A[0];
    10     for(int i=1;i<n;i++)
    11     {
    12         int left=0;
    13         int right=max;
    14         while(left<=right)            //这个二分查找就是最终left落到指定位置例如lis[]={1,2,4},若A[i]=5,left=3(从0开始),则更新为lis[]={1,2,4,5};lis[]={1,2,4},若A[i]=3,left=2,则更新为lis[]={1,2,3};
    15         {
    16             int mid=(left+right)/2;
    17             if(A[i]>lis[mid])
    18                 left=mid+1;
    19             else
    20                 right=mid-1;
    21         }
    22         lis[left]=A[i];
    23         lefttoright[i]=left+1;    //lefttoright[i]等于left加一,同返回时是max+1同样道理
    24         if(left>max)          //如果left>max,则让max=left
    25             max++;
    26     }
    27     delete lis;
    28     return max+1;             //注意,必须返回max+1,才是最终结果max是最长递增子序列长度减一
    29 }

    下面就开始实现“从一列数中筛除尽可能少的数使得从左往右看,这些数是从小到大再从大到小的“这个问题。
        双端LIS问题,用动态规划的思想可以解决,目标规划函数为max{B[i] + C[i] - 1},其中B[i]是从左到右的,0~i个数之间满足递增的数字个数;C[i]为从右到左的,n- 1 ~ i个数之间满足递增的数字个数。最后结果为n - max + 1,其中动态规划的时候,可以用二分查找进行处理,如上述求最长递增子序列的方法二。

    代码如下:
     1 #include <iostream>
     2 using namespace std;
     3 
     4 //最长递增子序列的O(nlogn)方法
     5 //lis[i]表示最长递增子序列的长度的i+1的最小的最后一个元素
     6 
     7 int findlis(int *A,int n,int *lefttoright)     //从左向右最长递增子序列  
     8 {
     9     if(A==NULL||n<0)
    10         return -1;
    11     int *lis=new int[n];
    12     //int *lefttoright=new int[n];
    13     lefttoright[0]=1;                    //lefttoright[i]保存从左到右,以i为终点的最长递增子序列长度,注意已经是正常的长度了,不是小一了
    14     int max=0;                           //max是lis[]的最大下标如lis[]={1,2,4}时,max=2;
    15     lis[0]=A[0];
    16     for(int i=1;i<n;i++)
    17     {
    18         int left=0;
    19         int right=max;
    20         while(left<=right)            //这个二分查找就是最终left落到指定位置例如lis[]={1,2,4},若A[i]=5,left=3(从0开始),则更新为lis[]={1,2,4,5};lis[]={1,2,4},若A[i]=3,left=2,则更新为lis[]={1,2,3};
    21         {
    22             int mid=(left+right)/2;
    23             if(A[i]>lis[mid])
    24                 left=mid+1;
    25             else
    26                 right=mid-1;
    27         }
    28         lis[left]=A[i];
    29         lefttoright[i]=left+1;    //lefttoright[i]等于left加一,同返回时是max+1同样道理
    30         if(left>max)          //如果left>max,则让max=left
    31             max++;
    32     }
    33     delete lis;
    34     return max+1;             //注意,必须返回max+1,才是最终结果max是最长递增子序列长度减一
    35 }
    36 
    37 int findrighttoleftincrease(int *A,int n,int * righttoleft)  //从右向左最长递增子序列,也可以说成是从左向右最长递减子序列
    38 {
    39     if(A==NULL||n<0)
    40         return -1;
    41     int *lis=new int[n];
    42     //int *righttoleft=new int[n];
    43     lis[0]=A[n-1];           //lis[0]=为A【n-1]
    44     righttoleft[n-1]=1;      //注意是lefttoright[n-1]=1
    45     int max=0;
    46     int left,right;
    47     for(int i=n-2;i>=0;i--)
    48     {
    49         left=0;
    50         right=max;
    51         while(left<=right)
    52         {
    53             int mid=(left+right)/2;
    54             if(A[i]>lis[mid])
    55                 left=mid+1;
    56             else
    57                 right=mid-1;
    58         }
    59         lis[left]=A[i];
    60         righttoleft[i]=left+1;
    61         if(left>max)                  //其实这时,max++后,max==left
    62             max++;
    63     }
    64     delete lis;
    65     return max++;
    66 }
    67             
    68 
    69 int main()
    70 {
    71     //网易的去掉最少元素使得从左向右递增然后递减,即为从左向右递增然后递减的最大值
    72     //Big=max(lefttoright[i]+righttoleft[i]-1}
    73     //所求即为n-Big。
    74     int A[]={2,3,5,1,6,9,10,15,1};
    75     int *lefttoright=new int[9];
    76     int *righttoleft=new int[9];
    77     int maxleft=findlis(A,9,lefttoright);
    78     if(maxleft==-1)
    79         cout<<"wrong"<<endl;
    80     else
    81         cout<<"max num lefttoright= "<<maxleft<<endl;
    82     int maxright=findrighttoleftincrease(A,9,righttoleft);
    83     if(maxright==-1)
    84         cout<<"wrong"<<endl;
    85     else
    86         cout<<"max num righttoleft= "<<maxright<<endl;
    87     int max=0;
    88     for(int i=0;i<9;i++)
    89     {
    90         if(lefttoright[i]+righttoleft[i]-1>max)
    91             max=lefttoright[i]+righttoleft[i]-1;
    92     }
    93     cout<<"去除"<<9-max<<endl;
    94     delete lefttoright;
    95     delete righttoleft;
    96     system("pause");
    97 }

    完。

  • 相关阅读:
    原生JS实现日历
    重复的事情让机器来做,简化的思想
    Ext3.1的一些使用讨论
    JS使用Crypto实现AES/ECS/zero-padding加密
    一些提升效率的小知识
    一些很有意思的JS现象
    Tiny Linux -- tce-load
    python sqlalchemy mysql 自动映射
    python 反射
    python 动态导包
  • 原文地址:https://www.cnblogs.com/zmlctt/p/3842501.html
Copyright © 2011-2022 走看看