📃題目
給定一個字串,找出包含最多2個不同字元的最長子串長度
示例 1:
輸入:axyxxxxyyyxdddadayx
輸出:10
解釋:其中符合條件的最長子串是 xyxxxxyyyx,子串中只包含x和y兩個不同字元
🤔思路
子串問題第一反應想到了動態規劃,開始動規五部曲分析
1、確定dp陣列以及下標含義
dp陣列應該是幾維呢?
設定一維的話,dp[i]中應該存放的是以下標i結尾符合要求的字串長度,但是在判斷字串是否符合要求時,我們還需要額外的資訊——該字串中不同字元的個數
因此dp陣列設定成為二維,即dp[i][j],i與一維的定義相同,而j表示以下標i結尾,有j+1個不同字元。
2、確定遞推公式
在遞推過程中存在兩種情況:
當前字元與前一個字元相等,即s[i]==s[i-1]
dp[i][0] = dp[i-1][0] + 1;
// 因為dp[i-1][1]>dp[i-1][0],所以不用考慮dp[i-1][0]+1的情況
dp[i][1] = dp[i-1][1] + 1;
當前字元與前一個字元不相等,即s[i]≠s[i-1]
//只能有一個不同字元,那麼只能是s[i]這一個了
dp[i][0] = 1;
dp[i][1] = dp[i-1][0] + 1;
dp[i][1]乍一看感覺沒問題,但是執行的時候,它忽略了一種場景,即以i-1結尾有兩個不同字元的字串,其中一個字元與s[i]相同,那麼這種情況應該是dp[i][1] = dp[i-1][1] + 1
那麼如何去判斷另外一個”不知道位置“的字元是否與s[i]相同呢
其實這個位置與dp[i-1][1]息息相關,可以透過i-dp[i-1][1]-1獲得另一個字元的下標位置,因為[i-dp[i-1][1],i-1]這個區間內的都是相同字元
所以第二種情況的遞推公式,程式碼如下:
//只能有一個不同字元,那麼只能是s[i]這一個了
dp[i][0] = 1;
if (s[i] != s[i - dp[i - 1][0] - 1]) dp[i][1] = dp[i - 1][0] + 1;
else dp[i][1] = dp[i - 1][1] + 1;
3、dp陣列如何初始化
陣列需要初始化的是dp[0][0]和dp[0][1],兩個分別表示下標i字元結尾有1個不同字元和有2個不同字元的字串長度,從實際結果上看長度都是1
4、確定遍歷順序
從遞推公式上可以看出,dp[i]需要dp[i-1]計算,所以應該從上至下計算
5、舉例推導公式
使用字串前半部分axyxxxxy為例
dp[i][j] | 0 | 1 |
---|---|---|
0 | 1 | 1 |
1 | 1 | 2 |
2 | 1 | 2 |
3 | 1 | 3 |
4 | 2 | 4 |
5 | 3 | 5 |
6 | 4 | 6 |
7 | 1 | 7 |
最終遍歷dp陣列尋找最大值得到結果
程式碼如下:
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
vector<vector<int>> dp(s.size(), vector<int>(2, 0));
dp[0][0] = 1;
dp[0][1] = 1;
for (int i = 1; i < s.size(); i++) {
if (s[i] == s[i - 1]) {
dp[i][0] = dp[i - 1][0] + 1;
dp[i][1] = dp[i - 1][1] + 1;
} else {
dp[i][0] = 1;
if (s[i] != s[i - dp[i - 1][0] - 1]) dp[i][1] = dp[i - 1][0] + 1;
else dp[i][1] = dp[i - 1][1] + 1;
}
cout << dp[i][1] << endl;
}
int result = dp[0][1];
for (int i = 1; i < s.size(); i++) {
result = max(result, dp[i][1]);
}
cout << result;
return 0;
}
🔚總結
本題屬於動態規劃型別的題目,只是在遞推公式的細節上需要琢磨,透過dp[i-1][0]處理不同字元是否重合是題目的關鍵,否則會卡測試案例
雖然是基礎演算法的稍微改進,但是自己構想寫出來並提交AC的時候,那種成就感讓人還想再來一題