求一串數字中——和最大的連續子序列; 求一串數字差值的絕對值最小的兩個數字
問題描述 :
從一組數字中,找出其所有連續子序列中,和數(子序列所有數字求和)最大的連續子序列:
如:陣列 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.程式碼與本文上一個演算法類似,略;