子序列个数

题目:

子序列的定义:对于一个序列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的不同子序列的数量。
输入: 长度为n的数组1<=n<=100,数组元素0<=a[i]<=110
输出:子序列 的个数对1000000007取余数的结果(由于答案比较大,输出Mod 1000000007的结果即可)。
函数头部: C/C++:   int run(cons int *a,int n); java   public static int run(int [] a); 

思路:

这个问题前后总共用了五十分钟(本人很水,请大神勿喷),第一次提交时出错,后来一差代码,是在状态转移的地方公式算错了。其实这道题主要思想就是动态规划,算出状态转移方程。

假设子序列的前k个数的子序列个数为d(k),那么前k - 1个子序列的个数就为d(k - 1)个子序列,从k - 1 到k的变化是怎样的呢?
  1、假设数组a[N]第k个数为a[k],如果a[k] 与前面的k - 1个数都不相同,那么就有 : d(k) = d(k - 1) + 【d(k - 1) + 1】 =  2d(k - 1) + 1,为什么呢?可以这样想,对于前k- 1项的子序列个数为d(k - 1),那前k个数,无非就是在前k - 1项的基础上多加了一个数a[k](a[k]与前k - 1个数任意一个都相等),那就在原来的组合上加上a[k],就有d(k - 1)个,还有一个a[k]自己构成一个子序列,所以还要加1;
  2、假设a[k] 与前面的k - 1个数其中一个相等,那依旧加上前k - 1个子序列个数 d(k - 1),但是由于前面有与a[k]相等的数,所以要减掉重复的部分,如何找到重复的部分呢,假设离k最近的一个与a[k]相等的数为第t个a[t] = a[k],即序列(a[1], a[2], ……,a[t],……,a[k - 1],a[k]),a[t] = a[k];我们已经知道序列(a[1], a[2], ……,a[t])的序列个数为d(t),那么d(t - 1)就是重复的部分,这里需要自己做好思考,也是算法的关键部分,这里我要解释的地方是,为什么只需要找到离k最近的t使得a[t] = a[k]?给出的解释是:我们是从1 - n对数组进行遍历的,计算d(i)的i就是从1到n依次计算的,那么第一次遇到a[k] = a[t]的情况满足条件:有且仅有一个t使得a[t] = a[k],比如序列(1, 2, 3, 2, 4, 2),分别计算d(1),d(2),d(3),d(4),d(5),d(6);我们在计算d(4)的时候发现a[4] = a[2](假设下标从1开始),所以d(4) = 2*d(3) - d(2 -1) = 2d(3) - d(1);当计算d(6)的时候也有a[6] = a[4] = a[2],但是由于我们前面已经把a[2]重复的部分减掉了,所以不需要再减,d(6) = 2 * d(5) - d(4 - 1) = 2d(5) - d(3).
  过程繁琐,我总结一下结论:
  状态转移方程为:
    d(k) = 2 * d(k - 1) + 1;   a[k] != a[i],i = 1,2,3……k - 1;
     d(k) = 2 * d(k - 1) - d(t - 1);   从k往前搜索,存在离k最近的t,使得a[t] = a[k].

代码:

package com.ylxfc.demo;

import java.math.BigInteger;

public class demo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		int a[] = new int[]{87 ,110 ,85 ,32 ,6 ,2 };
		System.out.println(run(a));
	}
	public static int run(int[] a) {
		int l = a.length;
		long res[] = new long[100]; 
		res[0] = 1;
		int m = 0;
		int temp = 0;
		for(int i = 1;i<l;i++) {
			temp = a[i];
			m = i-1;
			while(m >=0) {
				if(temp == a[m]) {
					break;
				}
				m--;
			}
			if(m>=1){
				res[i] = 2 * res[i - 1] - res[m - 1];
			}else if(m == 0) {
				res[i] = 2 * res[i - 1];
			}else {
				res[i] = 2 * res[i - 1] + 1;
			}
			if(res[i] >= 1000000007) {
				res[i] = res[i] % 1000000007;
			}
			if(res[i]<0) {
				res[i] += 1000000007;
			}
		}
		return (int)res[l-1];
	}
}   


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值