單調遞增最長子序列
- 描述
- 求一個字符串的最長遞增子序列的長度
如:dabdbf最長遞增子序列就是abdf,長度爲4
- 輸入
- 第一行一個整數0<n<20,表示有n個字符串要處理
隨後的n行,每行有一個字符串,該字符串的長度不會超過10000 - 輸出
- 輸出字符串的最長遞增子序列的長度
- 樣例輸入
-
3 aaa ababc abklmncdefg
- 樣例輸出
1 3 7
- 本題有多種解法:
- 一:最長公共子序列法
- 此方法用時較長,佔用內存也較大,且僅適用於有規律且有限長度的子序列中,代碼如下:
#include<stdio.h> #include<string.h> #define max(a,b) (a>b?a:b) int dp[10010][30]; int main(void) { char a[10010]; char b[30]={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'}; int n,len,i,j,k; int sum,max; scanf("%d",&n); while(n--) { scanf("%s",a); len=strlen(a); for(i=1;i<=len;i++) { for(j=1;j<=26;j++) { if(a[i-1]==b[j-1]) { dp[i][j]=dp[i-1][j-1]+1; } else { dp[i][j]=max(dp[i-1][j],dp[i][j-1]); } } } /* for(i=0;i<len;i++) { for(j=0;j<26;j++) { printf("%d ",dp[i][j]); } printf("\n"); } */ printf("%d\n",dp[len][26]); } return 0; }
二:O(n^2) 最大子段和法
此方法採用了標記數組,記錄每個當前狀態下的最優解。
本題中的核心代碼就是那兩個for循環來更新m和dp數組中的值,
其中第一個循環中,每次都須將m置零 ,
其中第二個for循環的意思就是更新m的值,
使m的值爲i位置字符之前的所有比i位置字符小的dp數組中所存值得最大值,直至第二個循環
結束,使i位置的dp數組爲m的值加上1,即可。
代碼如下:
#include<stdio.h> #include<string.h> char s[10010]; int dp[10010]={1}; int main(void) { int n,len; int i,j,m; scanf("%d",&n); while(n--) { scanf("%s",s); len=strlen(s); for(i=1;i<len;i++) { m=0; for(j=i-1;j>=0;j--) { if(s[i]>s[j]&&m<dp[j]) { m=dp[j]; } } dp[i]=m+1; } m=dp[0]; for(i=0;i<len;i++) { if(m<dp[i]) { m=dp[i]; } } printf("%d\n",m); } return 0; }
三:O(n^2) 此方法是方法二的優化,方法二中的標記數組過長,此方法採用標記字符串,所以標記數組的長度縮減到26即可,本代碼中用的長度爲30
本題中使用了標記字符串,先將待求字符串中的第一個字符賦到標記字符串中,
並使用k對標記字符串進行計數,
然後依次讀入待求字符串的字符,
並與標記字符串中的字符從後往前依次比較,
若標記字符串的字符比待求字符串的字符大,且此時處在標記字符串的起始位置
,則將此位置的待求字符賦到標記字符串中,
若標記字符串的字符比待求字符串的字符小,則將此處待求字符串的字符賦到標記字符串的下一個字符,如果此時已達到標記字符串的長度,則令k加1,並結束當前循環,重新開始比較,重新開始時,只有k增加了一,其餘各數據均不變。
代碼如下:
#include<stdio.h> #include<string.h> int main(void) { char s[10010]; char dp[35]; int n,len,i,j,k; scanf("%d",&n); while(n--) { scanf("%s",s); len=strlen(s); dp[0]=s[0]; k=1; for(i=0;i<len;i++) { for(j=k-1;j>=0;j--) { if(dp[j]>s[i]&&j==0) { dp[0]=s[i]; } if(dp[j]<s[i]) { dp[j+1]=s[i]; if(j==k-1) { k++; } break; } } } dp[k]='\0'; printf("%d\n",k); } return 0; }
四:O(n*lg n)
此方法是對方法三的優化,
最長遞增子序列可以優化到 O(n*lg n)
通過優化方法二中的s[i]在dp[]什麼位置,使用二分查找。
if(a[i]>dp[k]) {k=k+1;dp[k]=s[i];}
if(a[i]<dp[1]) dp[1]=s[i];
其他情況找到 dp[j]<s[i]<dp[j+1] dp[j+1]=s[i];
注:雖然說此方法是最優的解法,但是在oj上面提交的時候,並沒有方法三省時間,省內存。
#include<stdio.h> #include<string.h> #define N 10010 char s[N],dp[30]; int work(int n) { int i,j,k,x,y,m; dp[0]=s[0]; k=0; for(i=1;i<n;i++) { if(dp[k]<s[i]) { k++; dp[k]=s[i]; continue; } x=0; y=k; while(x<=y) { if(s[i]<dp[x]) { j=x; break; } if(dp[y]<s[i]) { j=y+1; break; } m=(x+y)/2; if(dp[m]<s[i]) { x=m+1; } else if(s[i]<dp[m]) { y=m-1; } else { j=m; break; } } dp[j]=s[i]; } return k+1; } int main(void) { int n,i; scanf("%d",&n); while(n--) { scanf("%s",s); printf("%d\n",work(strlen(s))); } return 0; }