题目描述
知识点
两种解法:
- 动态规划
- 滑动窗口
结果
实现
码前思考
- 题中提到了“最长”二字,然后又是“字符串”问题,让我很容易地就去想到了动态规划求解;
- 既然想从动态规划入手,那么就得想想怎么定义
dp
数组?因为做了很多这种动态规划的题目,我下意识地想到的就是最长无重复子串一定是以某个字符结尾的,那么我就直接定义
dp
数组的含义为以s[i]
结尾的最长无重复子串的长度。 - 上面只是设想
dp
数组是这个,那怎么证明这个dp
数组能满足最优子结构和重叠子问题呢?我们可以考虑对于字符
s[i]
,因为我们要求的是子串而不是子序列,所以s[i]
与s[i-1]
是有很大关系的,我们可以很容易得知,dp[i]
最好的结果就是dp[i-1]+1
,最坏的就是里面s[i-dp[i-1]]~s[i-1]
中存在了一个位置s[k]
==s[i]
,这样以s[i]
结尾的最长无重复子串的长度就变成了i-k
。 - 综上,可以得出代码了。
代码实现
//典型地动态规划题,划分子问题
//看见这种带最长,最短的就要想到最优化问题,想到最优化问题要想到动态规划
class Solution {
public:
int lengthOfLongestSubstring(string s) {
//获取长度
int size = s.size();
//特殊情况,进行特判
if(size == 0){
return 0;
}
//初始化动态规划数组
vector<int> dp(size);
//初始化递推边界条件
dp[0] = 1;
int ans = 1;
for(int i=1;i<size;i++){
//初始化为这个
dp[i] = dp[i-1]+1;
for(int j=i-1;j>=i-dp[i-1];j--){
if(s[j] == s[i]){
dp[i] = i-j;
}
}
ans = max(ans,dp[i]);
}
return ans;
}
};
码后反思
- 这道题目是典型的有关字符串动规的题目,状态数组
dp
的定义比较常规,但是状态转移方程就需要多思考了,我也是思考了很久才想到的。 - 注意,字符串问题并不一定就要使用动态规划,不要把自己的思维僵化了,比如这道题还可以使用滑动窗口。
- 滑动窗口解题:
其实这道题的滑动窗口思想和上面的动规思想接近。
代码:
//使用滑动窗口进行解题
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int size = s.size();
//进行特判
if(size == 0){
return 0;
}
int len = 1;
int left = 0;
int right = 0;
unordered_map<char,int> window;
while(right < s.size()){
char c = s[right];
right++;
//如果包含了这个字符,那么需要左移
if(window[c] == 1){
while(s[left] != c){
window[s[left]] = 0;
left++;
}
left++;
}else{
window[c] = 1;
}
if(right - left > len){
len = right - left;
}
}
return len;
}
};
其实这里的right
就是一个dp
数组,代表的是以当前right
为底,能够得到的最长距离,只不过这里由于使用了map
,从而使用空间复杂度换取了时间复杂度。所以会运行起来比动态规划解法要快很多!!!