二维DP
csharp
class Solution {
public:
int longestIdealString(string s, int k) {
int n = s.size();
vector<vector<int>> dp(n, vector<int>(26));
dp[0][s[0] - 'a'] = 1;
for(int i = 1; i < n; i++){
int currentChar = s[i] - 'a';
for(int prevChar = max(0, currentChar - k); prevChar <= min(25, currentChar + k); prevChar++){
dp[i][currentChar] = max(dp[i][currentChar], dp[i-1][prevChar] + 1);
}
dp[i][currentChar] = max(dp[i][currentChar], 1);
for(int j = 0; j < 26; j++){
dp[i][j] = max(dp[i][j], dp[i-1][j]);
}
}
int ans = 0;
for(int c = 0; c <= 25; c++){
ans = max(ans, dp[n-1][c]);
}
return ans;
}
};
时间复杂度:O(Nk)
空间复杂度:O(Nk)
对于子序列题目,我们通常可以定义一个二维数组dp[i][c]用来表示在s的前i个字符(i从0开始)并且以c字符作为结尾的最长理想子序列。
我们定义完dp后,首先对dp进行初始化:dp[0][s[0] - 'a'] = 1;
,代表s[0]自身作为子序列的长度为1。接下来我们遍历i从1到n,我们首先假设字符串s的s[i]为目前最长理想子序列的结尾,那么他可以由dp[i][currentChar] = max(dp[i][currentChar], dp[i-1][prevChar] + 1);
也就是可以从上一个状态i-1的所有可能的prevChar进行状态转移而来。
如果currentChar无法从任何一个prevChar进行状态转移而来,那么我们就令他为自身长度1,也就是 dp[i][currentChar] = max(dp[i][currentChar], 1);
。
最后假设我们不以s[i]作为结尾,那么当前的状态dp[i][j]则要继承上一个状态的dp[i-1][j]。
我们定义一个变量ans,用来记录s的前n个(即全部)字符并且以任何字符为结尾的理想子序列的最大值,并返回。
空间压缩
csharp
class Solution {
public:
int longestIdealString(string s, int k) {
int n = s.size();
vector<int> dp(26);
for(char c : s){
int maxLen = 0;
int curChar = c - 'a';
for(int prevChar = max(0,curChar - k); prevChar <= min(25, curChar + k); prevChar++){
maxLen = max(maxLen, dp[prevChar]);
}
dp[curChar] = maxLen + 1;
}
return *max_element(dp.begin(), dp.end());
}
};
空间复杂度压缩至O(1)
由于观察状态转移方程i都是由上一个状态i-1状态转移而来,那么我们可以采用滚动的方式进行空间压缩,我们定义dp[c]为以字符c为结尾的最长理想子序列的长度,并且而外定义一个变量maxLen来记录被转移状态的最长理想子序列的长度,所以当前的最长理想子序列长度就是dp[curChar] = maxLen + 1;
。