題目
給定一個大小為n的數組,要求寫出一個算法,求其最長的等差數列的子序列
分析
該題需要分幾種情況考慮。
1. 原數組是有序的,所要求的的子序列可以不連續。
對於數組arr[],不同的等差值d=1,2,3,4,5```(arr[max]-arr[min])可以求出不同的最長等差數列,然后在這些等差數列中求出最長的那個即可我們首先轉化為求一個數組的固定等差值的最長等差子序列。如數組1,2,4,6,8,9,求等差值2的最長等差子序列為2,4,6,8
1.1 固定等差值的最長子序列
求符合條件的最長子序列可以用動態規划來做,設dis[i]記錄數組arr[]的第1-i個元素子數組的最長等差子序列長度,狀態轉移方程式:
dis[i]=1;
dis[i]=dis[k]+1, k是數組第1-i個元素中,與元素i等差為d並且距離i最近的元素(k<i);
代碼
1 int RegularArithmeticSeq(int arr[],int len,int d) 2 { 3 if (arr==NULL||len<1) 4 throw std::exception("Invalid input."); 5 6 int* dis=new int[len+1](); 7 8 int re_len=0,re_index=0; 9 for (int i=1;i<=len;i++) 10 { 11 dis[i]=1; 12 for (int k=i;k>0;k--) 13 { 14 int cur_d=arr[i-1]-arr[k-1]; 15 16 if (cur_d==d&&dis[i]<=dis[k]) 17 { 18 dis[i]=dis[k]+1; 19 } 20 21 if (re_len<=dis[i]) 22 { 23 re_len=dis[i]; 24 re_index=i; 25 } 26 27 if (cur_d>d) 28 break; 29 } 30 } 31 32 int out=arr[re_index-1]; 33 for (int n=0;n<re_len;n++) 34 { 35 cout<<out<<' '; 36 out=out-d; 37 } 38 cout<<endl; 39 40 delete[] dis; 41 42 return re_len; 43 }
上述代碼的時間復雜度為O(n*n),時間復雜度為O(n);
對於尋找1-i中最后一個和i等差的元素k,除了順序遍歷,還可以用二分查找法進行一點優化,查找的時間復雜度為O(logn)
1 int find_k(int arr[],int n,int d) 2 { 3 int small=0,big=n-1,k=0,sum=0; 4 while (small<=big) 5 { 6 k=small+(big-small)/2; 7 sum=arr[k]+d; 8 if (sum<arr[n-1]) 9 { 10 small=k+1; 11 }else if(sum>arr[n-1]) 12 { 13 big=k-1; 14 }else 15 { 16 if (k<=big&&(arr[k+1]+d)!=sum) 17 { 18 return k; 19 }else 20 { 21 small=k+1; 22 } 23 } 24 } 25 26 return n-1; 27 }
優化代碼
1 int RegularArithmeticSeq2(int arr[],int len,int d,int& re_idx) 2 { 3 if (arr==NULL||len<1) 4 throw std::exception("Invalid input."); 5 6 int* dis=new int[len](); 7 8 int* dif=new int[len](); 9 for (int i=0;i<len;i++) 10 { 11 dif[i]=find_k(arr,i+1,d); 12 } 13 14 int re_len=0,re_index=0; 15 for (int i=0;i<len;i++) 16 { 17 dis[i]=1; 18 if (dif[i]!=i) 19 { 20 dis[i]=dis[dif[i]]+1; 21 } 22 if (re_len<=dis[i]) 23 { 24 re_len=dis[i]; 25 re_index=i; 26 } 27 } 28 29 int out=arr[re_index]; 30 re_idx=re_index+1; 31 for (int n=0;n<re_len;n++) 32 { 33 cout<<out<<' '; 34 out=out-d; 35 } 36 cout<<endl; 37 38 delete[] dis; 39 delete[] dif; 40 41 return re_len; 42 }
上述過程中時間復雜度降為O(n*logn)
1.2 非固定等差值的最長子序列
在解決了固定等差值得最長子序列后,就可以着手求非固定等差值的最長子序列了。等差值d的范圍是1,2···(arr[max]-arr[min]),循環着m個等差值,然后求出最長的等差子序列即可
時間復雜度(n*logn*m),m<=n-1;空間復雜度O(n*m)
代碼
1 int ArithmeticSeq(int arr[],int len) 2 { 3 if (arr==NULL||len<1) 4 throw std::exception("Invalid input."); 5 6 int max_d=arr[len-1]-arr[0]; 7 int re_len=0,re_d=0,re_index=0; 8 9 for (int d=1;d<=max_d;d++) 10 { 11 int idx=0; 12 int dlen=RegularArithmeticSeq2(arr,len,d,idx); 13 if(re_len<=dlen) 14 { 15 re_len=dlen; 16 re_d=d; 17 re_index=idx; 18 } 19 } 20 21 int out=arr[re_index-1]; 22 for (int n=0;n<re_len;n++) 23 { 24 cout<<out<<' '; 25 out=out-re_d; 26 } 27 cout<<endl; 28 29 return re_len; 30 }
也有另外一種解法:設dis[i][d]為數組中第1到i個元素中等差值為d的最長子序列的長度(序列可以不連續),則動態規划式;
dis[i][d]=1,默認等1,即在1-i個元素中等差值為d的最長子序列長度至少是1(i元素本身)
dis[i][d]=dis[k][d]+1, k為1-i個元素中最后一個與i元素的等差d的元素
代碼
1 int ArithmeticSeq(int arr[],int len) 2 { 3 if (arr==NULL||len<1) 4 throw std::exception("Invalid input."); 5 6 int max_d=arr[len-1]-arr[0]; 7 int** dis=new int*[len+1]; 8 for (int i=0;i<=len;i++) 9 { 10 dis[i]=new int[max_d+1](); 11 dis[i][0]=1; 12 } 13 14 int re_len=0,re_d=0,re_index=0; 15 for (int i=1;i<=len;i++) 16 { 17 for (int d=1;d<=max_d;d++) 18 { 19 dis[i][d]=1; 20 for (int k=1;k<i;k++) 21 { 22 int cur_d=arr[i-1]-arr[k-1]; 23 if (cur_d==d&&dis[i][d]<=dis[k][d]) 24 { 25 dis[i][d]=dis[k][d]+1; 26 } 27 28 if (re_len<=dis[i][d]) 29 { 30 re_len=dis[i][d]; 31 re_d=d; 32 re_index=i; 33 } 34 } 35 36 } 37 } 38 39 int out=arr[re_index-1]; 40 for (int n=0;n<re_len;n++) 41 { 42 cout<<out<<' '; 43 out=out-re_d; 44 } 45 cout<<endl; 46 47 for (int i=0;i<=len;i++) 48 { 49 delete[] dis[i]; 50 } 51 delete[] dis; 52 53 return re_len; 54 }
2. 原數組是無序的,所要求的的子序列可以不連續。
網上有不用排序的解法,但筆者覺得先排序還是更好些,也容易理解。當然,解此題的關鍵是找到在循環i和d的過程中找到k,對於非排序數組用順序查找k,也可以。
代碼略
3. 原數組是無序的,所要求的的子序列連續。
1 int ArithmeticSeq3(int arr[],int len) 2 { 3 if (arr==NULL||len<1) 4 throw std::exception("Invalid input."); 5 6 int re_len=1,d=1; 7 int max_len=0,re_d=0,re_index=0; 8 for (int i=1;i<len;i++) 9 { 10 if((arr[i]-arr[i-1])==d) 11 { 12 re_len=re_len+1; 13 }else 14 { 15 re_len=2; 16 } 17 d=arr[i]-arr[i-1]; 18 if (max_len<=re_len) 19 { 20 max_len=re_len; 21 re_d=d; 22 re_index=i; 23 } 24 } 25 26 int out=arr[re_index]; 27 for (int n=0;n<max_len;n++) 28 { 29 cout<<out<<' '; 30 out=out-re_d; 31 } 32 cout<<endl; 33 34 return re_len; 35 }