最大连续子序列 及 延伸题目

来源:互联网 发布:朽木充栋梁网络剧全集 编辑:程序博客网 时间:2024/06/01 09:58

最大连续子序列问题

问题定义
给定K个整数的序列{ N1, N2, …, NK },其任意连续子序列可表示为{ Ni, Ni+1, …, Nj },其中 1 <= i <= j <= K。最大连续子序列是所有连续子序列中元素和最大的一个, 例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和为20

解法1:朴素解法, 时间复杂度 O(K^2)

//假设给定序列:a1,a2,...,aKmaxsum=0; // 最大的连续子序列的和for(int i=0; i<K; i++){    tmpSum=0;    for(int j=i; j<K; j++){        tmpSum += a[j]        if(tmpSum > maxsum){            maxsum = tmpSum;        }    }}

解法2:分治算法, 时间复杂度:O(nlogn)
对于任意一个序列{a1, a2, …,am,…. an}, ( m=(1+n)/2 ) 最大的连续子序列在该序列中的位置存在三种情况: 1. 位于中间部分的左边; 2. 位于中间部分的右边 ; 3. 左边和右边都含有最大的连续子序列的一部分, e.g. ai, …, am, …., aj.
对于情况1,2, 使用递归算法可以轻松计算出;对于情况3, 则通过求出前半部分的最大和(包含前半部分的最后一个元素)以及后半部分的最大和(包含后半部分的第一个元素)而得到,然后将这两个和在一起, 最后,三种情况中最大的结果就是要求的结果。

int MaxSubSum(const int A[], int Left, int Right){  int MaxLeftSum,MaxRightSum;  int MaxLeftBorderSum,MaxRightBorderSum;  int LeftBorderSum,RightBorderSum;  int mid,i;  if(Left == Right) // 处理只有一个元素的子序列  {    if(A[Left] > 0)      return A[Left];    else // 对于小于等于0的元素,       return 0;  }  mid= (Left + Right)/2;  // 情况1  MaxLeftSum = MaxSubSum(A,Left,mid);  // 情况2  MaxRightSum = MaxSubSum(A,mid+1,Right);  // 情况3  MaxLeftBorderSum = 0;  LeftBorderSum = 0;  for(i = mid;i >= Left;i--)// 求解最大序列的左边部分  {    LeftBorderSum += A[i];    if(LeftBorderSum > MaxLeftBorderSum)      MaxLeftBorderSum = LeftBorderSum;  }  MaxRightBorderSum = 0;  RightBorderSum = 0;  for(i = mid+1;i <= Right;i++)// 求解最大序列的右边部分  {    RightBorderSum += A[i];    if(RightBorderSum > MaxRightBorderSum)      MaxRightBorderSum = RightBorderSum;  }   return Max(MaxLeftSum,MaxRightSum,MaxLeftBorderSum + MaxRightBorderSum); // 返回三种情况中最大的结果}

解法3: 动态规划 , 时间复杂度O(n)

引理1: 以负数开头的子序列不会是最大子序列。
证明:令 子序列开头的元素 ai < 0, 终结元素为aj, 则 ai+…+aj < ai+1+…+aj 显然成立。
引理2:对子序列{ai, …, aj}, 条件1:如果对x取{i~j}中的任意整数(包含i,不包含j) sum{ai, …, ax} >0, 但是,条件2:sum{ai, …, aj}<0,则以该子序列中的任何元素ap开头的以aj为终结的任意子序列的和必定小于0。
证明:已知aj<0 且由引理1知,ai > 0.
显然有 0 >= sum{ai, …, aj} >= sum{ai-1, …, aj}
反证法:假设sum{ap, …, aj}>0, 由引理2条件sum{a<sub>i</sub>, ..., a<sub>j</sub>}<0 知道sum{ai, …, ap-1}<0,但该结论又违反了引理2中的条件:如果对x取{i~j}中的任意整数(包含i,不包含j) sum{a<sub>i</sub>, ..., a<sub>x</sub>} >0,得证。

有引理1可知,若a[i]<0, 则应跳到a[i+1]作为子序列的开头元素(如果a[i+1]>0); 由引理2可知, 若a[i]+…+a[j]<=且满足引理2的其他条件,则应以a[j+1]作为子序列的开头元素(如果a[j+1]>0). 实质上,引理1是引理2的特例。
引理1和2可归结为该状态方程: maxsum(i)= max( maxsum(i-1)+ary(i), ary(i) ); (也可以由动态规划方法处理的准则:最优子结构”、“子问题重叠”、“边界”和“子问题独立”得到)
通过对给定序列顺序地反复运用引理1和引理2,最终可求得该序列的最大连续子序列。
代码如下:

int maxSubSeq(int[] ary){    int maxsum=0;    int localSum=0;    for (int i=0; i<ary.length; ++i){        localSum += ary[i];        if(localSum > maxsum){            maxsum= localSum;        }else if (localSum < 0){             localSum=0; // 不考虑 ai~aj中的元素作为子序列的开头, 其中ai>0, aj<0        }//else  => localSum >0, 就是引理2中的条件1    }}

不同子序列的个数

问题定义:
对于一个序列a=a[1],a[2],…a[n]。任意子序列可表示为{aPk, aPk+1, …, aPk+m},其中 1<= Pk < Pk+1< Pk+m<=n, 对于给定的序列,求不同的子序列的个数。

思路
令 f(i) 表示 原序列a中前 i 个数中含有的不同子序列个数,则
情况1:若原序列中第i+1个数与前 i 个数都不相同,则 f(i+1) = f(i)+f(i)+1.
这是因为,第i+1个数可以和前 i 个数中构成的 f(i)个不同的子序列结合形成的f(i)个子序列,且第i+1个数自己可以构成一个子序列, 再加上原来的f(i)个子序列,就是原序列中前i+1个数中含有不同子序列的个数。
情况2: 若原序列中第i+1个数与前 i 个书中存在相同的数, 则 f(i+1)=f(i)+f(i)-f( last(a[i+1]) ), 其中 last(a[i+1])是数字a[i+1]在原序列中最后一次出现的位置。e.g. 对序列{1, 2,2, 5, 4, 2, 3}, 令i+1=6, 则a[i+1]=2, 那么last(a[i+1])=3,而不是2.
之所以减去f( last(a[i+1]) ),是因为 a[i+1] 和前 last(a[i+1])-1个数字组合形成的子序列与前last(a[i+1])个数字形成的子序列相同
本例中, a[i+1]=26 (6代表其下标), 因为前 last (a[i+1])=3个数字:1, 2, 23 组成的子序列三个:{1}, {2},{1,2}。而a[i+1] 和前 last(a[i+1])-1 (=2)个数字组合形成的子序列和这三个相同,但是题目要求的是不同的子序列。

final MaxLen = 99999;int[] last = new int[MaxLen]; // 假设初始化为-1int[] f = new int[MaxLen];// Read inputsint n=scan.nextInt(); // read the length of the seqfor(int i=1; i<=n; ++i){    int x = scan.nextInt();    if(last[x]!=-1){        f[i] = f[i-1]*2 - f[last[x]];    }else{        f[i] = f[i-1]*2+1;    }    last[x]=i;}

可作为Fibonacci数列前缀的非空子序列个数 [微软面试题]

问题定义:
给定一个序列{an}, 求可作为fibonacci数列的非空子序列个数。 最后结果 mod 1,000,000,007!
输入
One line with an integer n.
Second line with n integers, indicating the sequence {an}.
For 30% of the data, n<=10.
For 60% of the data, n<=1000.
For 100% of the data, n<=1000000, 0<=ai<=100000.

fibonacci 数列:
F1 = 1, F2 = 1
Fn = Fn-1 + Fn-2, n>=3

样例输入
2 1 1 2 2 3
样例输出
7

分析
为区分样例中的每个元素, 假设重写样例为 21 12 13 24 25 36. 下标代表其在序列中索引号.
部分fibonacci数列: 1, 1, 2, 3, 5, 8, …..
符合条件的子序列: {12}, {13}, {12 13}, {12 13 24}, {12 13 25}, {12 13 25 36}, {12 13 24 36}

该问题可以用动态规划的思想解决: 先求长度 i 的子序列个数,然后利用子问题(长度 i 的子序列个数)求长度 i+1 的子序列个数。

f(i,j) 表示原数列中前i个数中,含有以fabonacci 数列中第 j项 (从下标1开始) 作为结尾的子序列的个数.

处理原数列中的非fabonacci数
由于任何含有非fabonacci数的子序列都一定不是fabonacci数的前缀,因此,应该对这类元素不做处理。

假设 第 i 个数是fobonacci数中的第 j 项, 则
如果原序列中第 i 个数 是 1 且 j 不等于1也不等于2:f(i, j)= f(i-1, j)
否则: f(i, j)= f(i-1, j-1) + f(i-1, j)
假设序列的长度为N, 则 answer = f(N,1)+…+f(N,K) , K 是规定fabonacci数列的最大数的下标。

对递推公式的解释:
f(i, j)= f(i-1, j-1) + f(i-1, j):
当 fibonacci 数 a[i] 在其前的元素里没有出现过时: 则在 第 i-1 个元素前的元素组成的 sub-sequence (fibonacci数列前缀) 和 a[i]组合, 同样可以形成 f(i-1, j-1)个符合条件的新 sub-sequence.
当 fibonacci 数 a[i] 在其前的元素里出现过时: 假设在前 i-1 个元素中,出现过和 a[i]相等的元素 a[i’] where i’ < i. 令 前 i-1 个元素中不包含 a[i’] 为 a[1..i] \ a[i’] , 则 a[1..i] \ a[i’] 和 a[i] 结合形成的符合条件的 新sub-sequence的数目 和 a[1..i] \ a[i’] 和 a[i’] 结合形成 的数目一样, 即:要加上f(i-1, j)。

边界条件
f(i,0)=1, for all i belong to {0, 1, 2, …, N} (N 是给定序列长度)
其他的初始化为0.

代码如下:

// 优化后的算法```javaint[] f = new int[25];f[0] = 1; // 边界条件int n = scan.nextInt();for(int i=1; i<=n; ++i){    int x = scan.nextInt();    if( x in fibonacci){        j = order(x) // 返回 x 在fibonacci数列中的下标, 如果x=1 则返回 2.         f[j] = (f[j]+f[j-1]);        if (x==1){            f[1] = f[1] + f[0] // f[0] 恒为1,不改变         }     }}int ans=0;for(int i=0; i<f.length; ++i){    ans = (ans+f[i]);}System.out.println(ans);
0 0
原创粉丝点击