算法题10 最长等差序列问题
题目
给定一个大小为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 }