求取子序列问题
更新日期:
题目
子序列的定义:对于一个序列a=a[1],a[2],……a[n],则非空序列a’=a[p1],a[p2]……a[pm]为a的一个子序列,其中1<=p1<p2<…..<pm<=n。 例如:4,14,2,3和14,1,2,3都为4,13,14,1,2,3的子序列。
- 对于给出序列a,有些子序列可能是相同的,这里只算做1个。
- 要求输出a的不同子序列的数量。
分析
直接分析好像比较麻烦,考虑的情况太多,故转向动态规划,进行step判决,以dp[i-1]表示a[0]…a[i-1]的子序列个数,添加一个树a[i],需要考虑的情况如下
1、a[i]在之前没出现过,则a[i]自身成为一个子序列+1,其次之前的每一个子序列与a[i]组合可以构成新的子序列,同时还需要包括之前的子序列,故+2dp[i-1],导出有如下递推关系
d[i] = 2d[i-1]+1
2、如果a[i]在之前出现过,则需要进行相应的扣除,首先自身成为的子序列需要扣除,则-1,同时上一次出现位置,所导致的增加的子序列需要扣除,则dp[上一次出现的位置-1]
d[i] = 2d[i-1]+1-1-d[上一次出现位置-1]
根据上述两个公式即可,当然在编程的时候需要注意一些细节,如上一次出现的位置在0处需要特别考虑。编程时,初始化条件
1 2 | dp[0] = 1; index[a[0]] = 0; |
完整代码下载见github-eesly
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | //获取子序列数 int subNum(int a[],int n) { //记录下标表示是否出现过,简单的hash int *index = new int[n]; memset((int*)index,-1,n*sizeof(int)); int *dp = new int[n]; memset((int*)dp,0,n*sizeof(int)); dp[0] = 1; index[a[0]] = 0; for (int i = 1; i<n; i++) { //如果前面没有相同的,则a[i]自身成为一个子序列,故+1 //还包括之前的子序列+dp[i-1] //还包括之前的子序列和a[i]构成新的子序列+dp[i-1],故总共有d[i-1]*2+1 if (index[a[i]] == -1) dp[i] = dp[i-1]*2+1; //如果前面已有相同的,首先假定与前面不同获得d[i-1]*2+1个子序列,然后再扣除 //情况1:和第一个数相同故只需要-1即可 else if(index[a[i]] == 0) dp[i] = dp[i-1]*2+1 -1; //情况2:和后面数相同,则首先a[i]自身构成的子序列已经重复,需-1 //同时,前一个位置的与a[i]相同的数位置,由于增加了该数所增加的全部子序列数需要扣除-dp[index[a[i]]-1]即可 else dp[i] = dp[i-1]*2+1 -1 - dp[index[a[i]]-1]; index[a[i]]=i; //根据上述推导,可以发现如果对每个子序列进行操作会是一个O(2^n)级的复杂度 } return dp[n-1]; } |