1. 程式人生 > >求一串數字中——和最大的連續子序列; 求一串數字差值的絕對值最小的兩個數字

求一串數字中——和最大的連續子序列; 求一串數字差值的絕對值最小的兩個數字

問題描述 :
從一組數字中,找出其所有連續子序列中,和數(子序列所有數字求和)最大的連續子序列:

如:陣列 int A[ ] = {-4 , 3 , 5 , -1};找出某幾個連續的子序列其和最大。比如A0+A1 = -1 。A1+A2+A3+A4 = 3。而A2+A3=8;則A2 A3組成的陣列即是所求。

 

求解方法1:

先寫我自己的方法,不是動態規劃,複雜度大概是O(n*n);

1,二維陣列ret[ ][ ];用上面的例子中陣列A[ ]= {-4 , 3 , 5 , -1}為例:

        ~  -4   3   5   1

0        0   0   0   0   0     //為了方便計算,第0行第0列均設為0

1        0  -4   3   5   1     //第1行表示子串長度為1時,包含該位置元素的子序列和數

2        0       -1   8   6     //第2行表示子串長度為2時,包含該位置元素的子序列和數

3        0             4   9

4        0                  5

其中,ret[i][j]位置的值為ret[i-1][j-1] + inp[j-1];

原理是,包含某個數x的子串長度為k的最大和數,等於x加上x之前的子串長度為k-1的最大和數;

即:上表中ret[4][5]=4,它是子序列長度為3,包含j=5處元素(即inp[4])的最大和數,是第5列以前的所有子序列長度為3-1=2的序列中,最大和數+該位的值:ret[3][4] + inp[4]=-1+5=4;

也就是說子序列長度為i的包含第j個位置的最大和數,是基於子序列長度為i-1的j前面的最大和數來求得的;

複製程式碼

#include<stdlib.h>
#include<stdio.h>
#define MAX 100 

int ret[MAX][MAX] = {{0}};//len+1行len+1列
int maxSubSeqSum(int inp[],int len){
        int maxret = 0;//最大的順序子串和的值
        int i = 1;//第0行和第0列都為0
        for(;i<len+1;i++){
                int j=i;
                for(;j<len+1;j++){
                        ret[i][j] = ret[i-1][j-1] + inp[j-1];
                        //ret由於第0行第0列值都為0,所以inp需要j-1
                        if(ret[i][j] > maxret) maxret = ret[i][j];
                        printf("ret[%d][%d]=%d\n",i,j,ret[i][j]);
                }
        }
        return maxret;
}

int main(){
        int input[] = {1,-1,4,-3,2};
        int len = sizeof(input)/sizeof(int);
        int ret = maxSubSeqSum(input,len);
        printf("max sub sequence sum is:%d\n",ret);
        return 0;
}

執行結果:

[email protected]:~/algorithm$ gcc maxSubSeqSum.c 
[email protected]:~/algorithm$ ./a.out
ret[1][1]=1
ret[1][2]=-1
ret[1][3]=4
ret[1][4]=-3
ret[1][5]=2
ret[2][2]=0
ret[2][3]=3
ret[2][4]=1
ret[2][5]=-1
ret[3][3]=4
ret[3][4]=0
ret[3][5]=3
ret[4][4]=1
ret[4][5]=2
ret[5][5]=3
max sub sequence sum is:4

 

 

求解方法2:

1,遞迴公式:f(n)表示包含元素A(n)的最大子序列和,它的最大值,要麼=A(n),要麼=

A(n)+f(n-1);

2,複雜度為O(n);

複製程式碼

#include<stdlib.h>
#include<stdio.h>
#define max(a,b) (a>b)?a:b

int maxSubSeqSum(int inp[],int inplen){
        int presum=*inp,ret=0,i=1;//ret為最大和,presum是f(n-1)的值
        for(;i<inplen;i++){
                presum += inp[i];
                int tmpmax = max(presum,inp[i]);
                if(tmpmax > ret) ret=tmpmax;
        }
        return ret;
}

int main(){
        int input[]={1,-1,4,-3,2};
        int length = sizeof(input)/sizeof(int);
        int ret =maxSubSeqSum(input,length);
        printf("result is:%d\n",ret);
        return 0;
}

複製程式碼

[email protected]:~/algorithm$ ./a.out
result is:4

(回過頭來看以前的程式碼,方法2其實不對,後續改進)

掃描演算法(O(n))

 

1,遞迴公式:f(n)表示包含元素A(n)的規模為x[0…n]的問題。如何擴充套件為包含A(n+1)的規模為x[0…n+1]的問題f(n+1)?

2,我們用類似分治演算法的原理:前i個元素中,最大總和子陣列要麼在前i個元素中,要麼其結束位置為i+1;

    舉個例子:

  int A[ ] = {-4 , 3 , 5 , -1};

      tmp = {  0 , 3 , 8 ,  7} 其中tmp = Max( tmp + A[i] , 0)

      max = {  0 , 3 , 8 ,  8} 其中max = Max(max, tmp);

tmp:從左往右掃描,第i位存的是包含第i位的最大子陣列的和,如果和數小於0則存0;

max:從左往右掃描,第i位之前最大子陣列的和,這個子陣列可以不包含i;說白了就是第i位存i左側所有tmp的最大值;

最終,max的最後一位8,即為和最大的連續子序列

下面看看程式碼:

複製程式碼

#include <stdio.h>
#include <stdlib.h>
#define max(a,b) a>b?a:b

int fun(int A[],int n){
  int i=1,maxret=0,tmp=max(A[0],0);
  for(;i<n;i++){
    tmp=max(0,tmp+A[i]);
    maxret=max(maxret,tmp);
    printf("tmp=%d,    maxret=%d\n",tmp,maxret);
  }
  return maxret;
}

int main(){
   int A[] = {-2,7,1,-6,-2,9,2,-1};
   int ret = fun(A,8);
   printf("%d\n",ret);
   return 0;
}

複製程式碼

複製程式碼

[[email protected] Desktop]# ./a.out
tmp=7,    maxret=7
tmp=8,    maxret=8
tmp=2,    maxret=8
tmp=0,    maxret=8
tmp=9,    maxret=9
tmp=11,    maxret=11
tmp=10,    maxret=11
11

複製程式碼

 

 

方法3:

最簡單的思路,遍歷所有子序列,找出其中最大的;

複製程式碼

int A[] = {31,-41,59,26,-53,58,97,-93,-23,84}; 

int fun(int A[], int n){
   int max = 0,i = 0;
   for(i=0;i<n;i++){
      int tmpsum = 0, j = 0;
      for(j=i;j<n;j++){
         tmpsum += A[j];
         if(tmpsum>max) max=tmpsum;
      }
   }
   return max;
}

void main(){
   int ret = fun(A,10);
   printf("%d",ret);
}

複製程式碼

Output:
187

 

方法4:

分治演算法解決方案:解決規模為n的問題,可以遞迴地解決規模近似為n/2的子問題,然後對答案進行合併,得到整個問題的答案;

把int A[] = {31,-41,59,26,-53,58,97,-93,-23,84};

分為:31,-41,59,26,-53,

         58,97,-93,-23,84

兩個子問題Sa,Sb;

現在,最大子向量必定在Sa,Sb或者跨越Sa和Sb之間邊界的部位Sc;

我們分治遞迴Sa,Sb,並通過類似方法3的遍歷的方式計算Sc;

程式碼如下:

複製程式碼

#define Max(a,b,c) ((a>b?a:b)>c?(a>b?a:b):c)


int A[] = {31,-41,59,26,-53,58,97,-93,-23,84};

//divide-and-conquer algorithm
int fun(int A[], int low, int high){
   int mid=(low+high)/2;
   int tmp=0,lret=0,rret=0,i=mid,j=mid+1;
   if(low>high) return 0;
   if(low == high) return Max(A[low],0,0);
   for(;i>=low;i--){
      tmp+=A[i];
      lret=Max(tmp,lret,0);
   } 
   tmp=0;
   for(;j<=high;j++){
      tmp+=A[j];
      rret=Max(tmp,rret,0);
   }
   return Max(lret+rret,fun(A,low,mid),fun(A,mid+1,high));
}

int main(){
   int ret = fun(A,0,9);
   printf("%d",ret);
}

複製程式碼

Output:
187

 

 

問題2:

 問題描述:

1.求一個串中差值最小的兩個數;

2,比如{2,6,3,8,11} 差值最小的顯然是2和3,差值絕對值為|2-3|=1;

 

問題解決:

1.就以A={2,6,3,8,11} 為例,相鄰位逐位相減得到B={a0-a1,a1-a2,a2-a3,a3-a4}={-4,3,-5,-3}

2.如果我們相求a2-a4=3-11,我們只需要用B中b2+b3=(a2-a3)+(a3-a4)= a2-a4=-5-3=-8;

 如果我們相求a0-a3=2-8,我們只需要用B中b0+b1+b2=(a0-a1)+(a1-a2)+(a2-a3)= -4+3-5=-6;

3.綜上分析,求任意|a(i)- a(j)|的最小值,就是求陣列B的和值絕對值最小的連續子序列(上一題是和值最大的連續子序列)與上一題相似;

4.程式碼與本文上一個演算法類似,略;