值最大子串,最長無重複子串,最長無重複子序列,最長公共子串,最長公共子序列解法及程式碼
1.值最大子串
比如{5,-3,4,2}的最大子序列就是 {5,-3,4,2},它的和是8,達到最大;而 {5,-6,4,2}的最大子序列是{4,2},它的和是6。
思路:看子串和是否大於0,大於0繼續加,小於0就從當前開始。
int maxSubSum(const vector<int> & arr,int &begin,int &end){ int maxSum=0; int currSum=0; int newbegin=0; for(int i=0;i<arr.size();++i){ currSum+=arr[i]; if(currSum>maxSum){ maxSum=currSum; begin=newbegin; end=i; } if(currSum<0){ currSum=0; newbegin=i+1; } } return maxSum; }
2. 最長無重複子串
子串(substring)——在字串中是連續的
子序列(subsequence)——在字串中可以不連續,也可以連續
思路:藉助一個雜湊表儲存每個字元出現的位置
A)如果第i個字元之前沒有出現過,那麼f(i) = f(i-1)+1;
B)如果第i個字元之前出現過,記第i個字元和它上出現在字串中的位置的距離,記為d。如果d>f(i-1),那麼仍然有f(i) = f(i-1)+1;如果d<=f(i-1),則說明第i個字元上次出現在f(i-1)對應的不重複字串之內,那麼這時候更新 f(i) = d
class Solution { public: int longestSubstringWithoutDuplication(const string& str) { int curLength = 0; int maxLength = 0; int* position = new int[26]; for (int i = 0; i < 26; ++i) position[i] = -1; for (int i = 0; i < str.length(); ++i) { int prevIndex = position[str[i] - 'a']; if (prevIndex < 0 || i - prevIndex > curLength) ++curLength; else { if (curLength > maxLength) maxLength = curLength; curLength = i - prevIndex; } position[str[i] - 'a'] = i; } if (curLength > maxLength) maxLength = curLength; delete[] position; return maxLength; } };
class Solution { public: int lengthOfLongestSubstring(string s) { int m[256]={0},maxlen=0,left=0; for(int i=0;i<s.length();++i) { if(m[s[i]]==0 || m[s[i]]<left) //從未出現過和未在當前滑動視窗內 maxlen = max(maxlen,i-left+1); else { left = m[s[i]]; } m[s[i]] = i+1; } return maxlen; } };
3.最長無重複子序列
思路:沒用雜湊表,只需要看之前是否出現過。也可以用個雜湊表更快。
int lengthOfLongestSubstring(string s) {
int max_len = 0, flag;
for(int i = 0; i < s.length(); i++){
flag = 1;
for(int j = 0; j < i; j++){
if(s[i] == s[j]){
flag = 0;
break;
}
}
if(flag){
max_len++;
}
}
return max_len;
}
4.最長公共子序列
子串(substring)——在字串中是連續的
子序列(subsequence)——在字串中可以不連續,也可以連續
示例:比如cnblogs和belong兩個字串,bo, bg, lg,blo,blog都在兩個 字串中出現過, 並且出現順序一致,求最長公共子序列 為blog。最長公共子串為lo。
思路:
暴力解法
假設 m<n, 對於母串X,我們可以暴力找出2的m次方個子序列,然後依次在母串Y中匹配,演算法的時間複雜度會達到指數級O(n∗2的m次)。顯然,暴力求解不太適用於此類問題。
動態規劃
假設Z=<z1,z2,⋯,zk>是X與Y的LCS, 我們觀察到
如果Xm=Yn,則Zk=Xm=Yn,有Zk−1是Xm−1與Yn−1的LCS;
如果Xm≠Yn,則Zk是Xm與Yn−1的LCS,或者是Xm−1與Yn的LCS。
因此,求解LCS的問題則變成遞迴求解的兩個子問題。但是,上述的遞迴求解的辦法中,重複的子問題多,效率低下。改進的辦法——用空間換時間,用陣列儲存中間狀態,方便後面的計算。這就是動態規劃(DP)的核心思想了。
DP求解LCS
用二維陣列c[i][j]記錄串x1x2⋯xi與y1y2⋯yj的LCS長度,則可得到狀態轉移方程
int lcs(String str1, String str2) {
int len1 = str1.length();
int len2 = str2.length();
int **c = new int*[len1+1];
for(int i=0;i<len1+1;++i)
c[i]=new int[len2+1];
for (int i = 0; i <= len1; i++) {
for( int j = 0; j <= len2; j++) {
if(i == 0 || j == 0) {
c[i][j] = 0;
} else if (str1[i-1] == str2[j-1]) {
c[i][j] = c[i-1][j-1] + 1;
} else {
c[i][j] = max(c[i - 1][j], c[i][j - 1]);
}
}
}
return c[len1][len2];
}
5.最長公共子串
思路:同樣可以用DP來解決。定義陣列的儲存含義對於後面推導轉移方程顯得尤為重要,糟糕的陣列定義會導致異常繁雜的轉移方程。考慮到子串的連續性,將二維陣列c[i][j]用來記錄具有這樣特點的子串——結尾同時也為為串x1x2⋯xi與y1y2⋯yj的結尾——的長度。
得到轉移方程:
最長公共子串的長度為 max(c[i,j]), i∈{1,⋯,m},j∈{1,⋯,n}。
int longsubstr(String str1, String str2) {
int len1 = str1.length();
int len2 = str2.length();
int result = 0; //記錄最長公共子串長度
int **c = new int*[len1+1];
for(int i=0;i<len1+1;++i)
c[i]=new int[len2+1];
for (int i = 0; i <= len1; i++) {
for( int j = 0; j <= len2; j++) {
if(i == 0 || j == 0) {
c[i][j] = 0;
} else if (str1[i-1] == str2[j-1]) {
c[i][j] = c[i-1][j-1] + 1;
result = max(c[i][j], result);
} else {
c[i][j] = 0;
}
}
}
return result;
}
參考部落格:
https://www.cnblogs.com/zhaogl/p/6364654.html
https://blog.csdn.net/u012102306/article/details/53184446
https://blog.csdn.net/wangdd_199326/article/details/76464333