赞
踩
给定一个数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\j | 2 | 3 | 4 |
---|---|---|---|
1 | 0 | 0 | 0 |
2 | 1 | 0 | 0 |
3 | 1 | 0 | 0 |
4 | 2 | 1 | 0 |
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;
}

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。