字符串 指针跳步思路
3. Longest Substring Without Repeating Characters
(1) Longest Substring Without Repeating Characters - LeetCode
思路:双指针动态维护
begin指针指向当前查找无重复的初始位置,cur指针向前遍历并完成查找。
1:s[cur]在begin-cur范围内出现过,begin收缩至出现位置i的下一个(注意字符串问题经常有这种指针跳步情况需要处理),重新开始记录本轮查找路径,cur继续向前遍历。
2:s[cur]在begin-cur范围内未出现过,cur继续向前遍历
3:更新结果
具体实现的思考:
1.要判断s[cur]是否出现过,考虑使用字典思想
且s[cur]是字符类型,不能像整数类型一样使用数组直接进行哈希,考虑使用map作为查找表。
使用map<string,int>,键为s[cur],出现过的元素存在map中
2.cur向前查找某个元素时需要与之前已经出现的字符串对比→查找map
map.find的平均time complexity为多少?是基于map的红黑树性质吗?
3.如果出现过,begin需要跳转到i+1
map值为位置下标i,方便查找时进行跳转,对s[cur]进行查找时若已出现,则表中记录的i正好为上一次s[cur]出现的位置下标
4.跳转后需要重新开始本轮记录
由begin跳到i收缩窗口时需要丢弃begin~i的查找表记录,故每次收缩窗口后需要更新map记录
实现代码一:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int res = 0;
//map记录本轮已经出现过的
unordered_map<char,int> occured;
int begin = 0;
for(int cur=0;cur<s.size();cur++){
char cur_str = s[cur];
if(occured.find(cur_str) != occured.end()){//出现过
//出现过
int i = occured[cur_str];//上一次出现的位置
for(int j=begin;j<=i;j++){//清除begin~i
occured.erase(occured.find(s[j]));
}
begin = i + 1;//begin跳转
}
//未出现过
occured.insert(pair(cur_str,cur));//更新map
res = getmax(res,cur-begin+1);
}
return res;
}
int getmax(int a,int b){
return a>b?a:b;
}
};
哈希思想的常用操作是整数类型的数组实现,这里能否使用?
对字符串的查找另一个思想是转换为ASCII码的存储数字,这就转化成了整型
基于ASCII的特点,需要0~255范围的字符,故所需整型数组大小为256
数组下标为字符对应的数值,数组值为字符位置下标,这样查找单个字符的时间复杂度为O(1)
初始时未记录任何元素位置,数组值应该初始化为-1,故begin只能定义为窗口的上一个位置
若一个元素重复出现了,那么第一次出现时的位置一定在本次(第二次)的前面,但这个第一次出现的位置可能并不一定是本轮所判断的窗口范围(begin,cur】
只有在窗口范围的才能进行进行处理,窗口以外的将被丢弃
往前遍历时,本次元素s[cur]有三种情况:
1、未出现过
2、出现过且在本轮窗口内
3、出现过但并不在本轮窗口内(begin,cur】==未出现过
所以本轮未出现过的情况相当于有两种:1&3,等价于Amap[s[cur]] == -1 或 Amap[s[cur]] >begin
综上,本轮未出现过的情况是Amap[s[cur]] >begin
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int res = 0;
vector<int> Amap(256,-1);//记录字符串出现的位置实现哈希
int begin = -1;//维护窗口的前一个元素
for(int cur=0;cur<s.size();cur++){
if(Amap[s[cur]] > begin){//在窗口范围内&出现过——此时哈希数组的值就是其上一次出现的位置
begin = Amap[s[cur]];
}
Amap[s[cur]] = cur;
res = getmax(res,cur-begin);
}
return res;
}
int getmax(int a,int b){
return a>b?a:b;
}
};
要记录无重复的最大子字符串长度,无重复即出现次数仅一次,一旦遇到出现次数>1次的字符则说明当前窗口存在重复字符,begin需要向前移动到这个重复字符后一个位置来收缩窗口,故
可从出现次数角度处理记录,找到出现次数>1的元素就说明找到了begin欲跳转的位置的下一位
实现代码三:
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int res = 0;
unordered_map<char,int> occurtime;//值记录出现次数
int begin = 0;
for(int cur=0;cur<s.size();cur++){
char cur_str = s[cur];
occurtime[cur_str] ++;//更新出现次数
while(occurtime[cur_str] > 1){//未更新到重复字符
occurtime[s[begin]] --;
begin++;
}
res = getmax(res,cur-begin+1);
}
return res;
}
int getmax(int a,int b){
return a>b?a:b;
}
};