获得递增子序列的个数

给定一个数n,和一个含n个数的序列,求出本质不同的上升子序列的个数。例如:
n=6,{3,1,2,1,3,4}
本质不同的上升子序列共有11种:
{3,4}
{1,2}
{1,3}
{1,4}
{2,3}
{2,4}
{1,2,3}
{1,2,4}
{1,3,4}
{2,3,4}
{1,2,3,4}

这是一道笔试题,同学笔试时候让我帮着做,知道用动态规划做,但是还是没做出来,没帮上忙,后来慢慢想了一下,感觉思路应该是对的。

动态规划。
dp[i][j]表示已i为结尾的长度为j的上升子序列的个数。对于序列s中第i个数s[i]前面的每个数s[k],若s[k] < s[i],那么dp[i][j]=dp[i][j]+dp[k][j-1]。意思就是在s[i]前面的一个数s[k],如果比s[i]小,那么以第i个数为结尾的长度为j的上升子序列的个数,就会增加 以第k个数为结尾的长度为j-1的上升子序列的个数 。对于第i个数前面的每个数都遍历一下,累加和。
那么对于重复的情况如何消除,最开始我想的是用一个列表来存储已经用过的数字,然后从前向后,用过的数字就存进去,之后就不再用这个数字了。比如说下面这个序列{1,2,5,3,5,7}
当计算以第六个数(数字7)为结尾的长度为3的上升子序列的个数时,dp[6][3]会累加前面的dp[3][2]和dp[5][2](也就是以第一个数字5为结尾的长度为2的上升子序列的个数 和 以第二个数字5为结尾的长度为2的上升子序列的个数)。那么第一个5用过了,存进列表,第二个5就不用了,似乎有道理,但是仔细想想,第一个数字5的长度为2的子序列有{1,5}、{2,5},而第二个数字5不仅包含这两个,还包含{3,5},那么结果肯定不对。所以,可以从后向前遍历,那么就不会重复计算相同数字的子序列,也不会漏掉。

当遍历结束完之后,dp矩阵中存的是以每个数i(1<=i<=n)为结尾的长度为j(1<= j < =n)的上升子序列个数。计算最终结果时,直观的想就是把所有的dp[i][j]求和。(1<=i<=n , 2<= j < =n)(注意j从2开始)。但是看下面这个序列{1,2,2,4},它的dp矩阵是下面这样的:

i\j234
1000
2100
3100
4210

dp[3][2]=1 代表子序列{1,2}。dp[2][2]=1地表子序列{1,2}。重复,所以在最终累加求和时,也要去重。同样的从后向前遍历,用过的数不再累加。

看一下代码:

public static int getLCS(int n,int[] s){
        List<Integer> flag=new ArrayList<Integer>();//去重用的列表
        int res=0;
        int[][] dp=new int [n+1][n+1];
        for(int i=1;i<=n;i++) {  
            for(int j=1;j<=2;j++){  
                dp[i][j]=0;  
                }  
            dp[i][1]=1;//以每个数为结尾的长度为1的子序列个数初始化为1  
            }  
        for(int i=1;i<=n;i++) {  
            for(int k=1;k<=i;k++)  {//以第i个数为结尾的上升子序列长度可能种类有k={1,2,...i},例如i=3,k={1,2,3} 
                flag.clear();
                for(int j=i-1;j>=1;j--)//从后向前 
                    if(s[j-1]<s[i-1]){//第i个数前面的数小     
                        if(!flag.contains(s[j-1])){//没用过
                            flag.add(s[j-1]);
                            dp[i][k]=dp[i][k]+dp[j][k-1];
                            }   
                        else
                            continue;      
                }
            }  
        }
        /*输出dp矩阵方便看结果,可以注释掉
        for(int i=1;i<=n;i++){
            for(int j=2;j<=n;j++){
                System.out.print(" "+dp[i][j]);
            }
            System.out.println();
        }*/
        //下面计算最终结果
        flag.clear();
        for(int i=n;i>=1;i--) {//从后向前
            if(!flag.contains(s[i-1])){
                flag.add(s[i-1]);
                for(int j=2;j<=n;j++){//长度为2到n
                    res=res+dp[i][j]; 
                }
            }
            else{
                continue;
            }
        }
        System.out.println(res);
        return res;
    }
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值