题源:LeetCode
3.无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
示例 4:
输入: s = “”
输出: 0
提示:
0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
我的思路:1.遍历找子串 2.判断子串是否重复(暴力或者KMP)并把重复次数记录下来 3.输出重复次数最少的字串
错误点:并没有注意到题中说的是“无重复字符”的子串。
答案思路:
关键字:
重复字符->出现1次->模式识别1:一旦涉及出现次数,需要用到散列表。
构造子串,散列表存下标->识别模式2:涉及子串,考虑滑动窗口
滑动窗口
什么是滑动窗口?
其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列。
如何移动?
把队列的左边的元素移出,直到满足题目要求。
一直维持这样的队列,找出队列出现最长的长度时候,求出解。
时间复杂度:O(n)
class Solution{
public:
int lengthOfLongestSubstring(string s) {
//s[start,end) 前面包含 后面不包含
int start(0), end(0), length(0), result(0);
int sSize = int(s.size());
while (end < sSize){
//每次挪动end一个长度,可以覆盖全部子字符串
for (int index = start; index < end; index++)//index[start,end)
//start之前的肯定有重复字符,所以不考虑
if (s[index] == s[end]){
//如果[start,end)中有与end重复的字符,则砍掉左边[start,index]。剩下[index+1,end)
start = index + 1;
length = end - start;//[start,end)的长度
break;//只可能有一个重复的字符,所以找到了就退出for循环
}
length++;//[start,end]的长度
result = max(result, length);//更新结果
end++;//end向后移动一个字符
}
return result;
}
};
用散列表可以进一步优化
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_set<char> lookup;
int result(0);
int start(0);
for(int end = 0; end < int(s.size()); end++){
while (lookup.find(s[end]) != lookup.end()){//里面能找到
lookup.erase(s[start]);
start++;
}
lookup.insert(s[end]);
result = max(result,end-start+1);
}
return result;
}
};
我发现我是反向优化了,代码变短了,耗时变高了。好吧,就算熟悉了一下STL的unordered_set。
5. 最长回文子串
给你一个字符串 s,找到 s 中最长的回文子串。
示例 1:
输入:s = “babad” 输出:“bab” 解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd” 输出:“bb”
示例 3:
输入:s = “a” 输出:“a”
示例 4:
输入:s = “ac” 输出:“a”
提示:
1 <= s.length <= 1000
s 仅由数字和英文字母(大写和/或小写)组成
我的思路:暴力解法。
答案思路:
解法一:中心扩展法
回文中心的两侧互为镜像。
因此,回文可以从他的中心展开,并且只有 2n-1 个这样的中心(一个元素为中心的情况有 n 个,两个元素为中心的情况有 n-1 个)
class Solution {
public:
pair<int, int> expandAroundCenter(const string& s,int left, int right){
while(left>=0 && right<s.size() && s[left] == s[right]){
left--;
right++;
}
return {left+1, right-1};//最左和最右一样,所以要回到上一个状态
}
string longestPalindrome(string s) {
int start(0),end(0);
for(int i = 0; i<s.size();i++){
auto [left1, right1] = expandAroundCenter(s, i, i);//中心是一个字符
auto [left2, right2] = expandAroundCenter(s, i, i + 1);//中心是两个字符
//找最大的距离
if(right1 - left1 > end - start){
start = left1;
end = right1;
}
if(right2 - left2 > end - start){
start = left2;
end = right2;
}
}
return s.substr(start, end-start +1);
}
};
易错点:pair<int, int>的return返回值用{}不用[]。auto用[]不用{}。
解法二:动态规划
我理解的动态规划就是长的字符串是不是回文串可以通过它的子串的状态和一些其他条件判断。
从最短的子串开始打表,然后按这个表慢慢增加字符串长度并作出判断。
总之要做的事情:0.找到边界、状态集合 1.从最短的子串打表 2.按表边增加字符串长度做出判断,边把自己的状态也写进表里(注意这里增加字符串长度指的是:第一次遍历,算以每一个字符开始长度为1的子串是否为回文串;第二次遍历,算以每一个字符开始长度为2的子串是否为回文串;第三次遍历,算以每一个字符开始长度为3的子串是否为回文串……而不是先确定从哪个字符开始,再第一次遍历,算以这个确定字符开始长度为1的子串是否为回文串;第二次遍历,算以这个确定字符开始长度为2的子串是否为回文串;第三次遍历,算以这个确定字符开始长度为3的子串是否为回文串……,总而言之,为了打表用表,先算短的字符串,算完全部短的字符串,再算长一点的字符串)
dp[i,j]表示从第i个字符到第j个字符的子串
边界:dp[i, i]=true || if( s[i] == s[i+1]) dp[i, i+1]=true ;i<j
表:纵列表示子串左边界,横行表示子串右边界,因为i<j,所以打表只填写右上方的三角形
状态:dp[i, j] = true 是回文串;dp[i, j] = false 不是回文串,或者非法i>J
dp[i+1,j-1]是回文串 && s[i]==s[j] => dp[i,j] 也是回文串
class Solution {
public:
string longestPalindrome(string s) {
int n = s.size();
// dp[left][right]标记从i到j是否是回文串
vector<vector<int> > dp(n, vector<int>(n));
string ans;
// length表示判断的字串的长度
// left表示字串的左边起始位置
// right表示字串的右边起始位置
for(int length = 1; length <= n; length++){//length = 0 是非法的
for(int left = 0; left + length-1 < n; left++){
int right = left + length -1;
// 即字符串长度为1时,矩阵对角线
if(length == 1) dp[left][right] = 1; //边界1
// 字符串长度为2的时候,只需判断两者是否相等
else if(length == 2) dp[left][right] = (s[left] == s[right]); //边界2
else{ // 字符串长度大于等于3之后
// 其是否是回文串取决于当前left和right及更小一号的字符串
// 更新参考值为矩阵表的左下方
dp[left][right] = (s[left] == s[right] && dp[left + 1][right - 1]);
}
// 如果当前left位置到right位置的字串能够构成回文串,并且现在长度大于之前记忆中的子回文串的长度,那么更新回文串!这里也可以优化成记录起始位置和长度的两个int,返回时再截取
if(dp[left][right] && length > ans.size()){
ans = s.substr(left, length );
}
}
}
return ans;
}
};