2022-05-31 03:13阅读: 41评论: 0推荐: 0

力扣-3-无重复字符的最长字串

做之前看题目的印象是“动态规划”,因为好像和以前做的“最大子数和”看起来有点像

查看标签有“滑动窗口”,这个见过听过,但是不知道具体是什么,但是有印象,计网里的拥塞控制也用到了“滑动窗口”的概念

感觉题目降低难度了,只是要求返回长度不是要求返回子串

有了计网“滑动窗口”的概念,我想是需要两个指针来表示窗口的,头指针表示窗口起始,尾指针表示窗口末尾。另外拿一个变量来记录窗口长度

感觉难点在于:

  1. 如何高效地判重
    我想起了刚刚做的题,用set,如果把字串中的字符放到set中,然后用count函数来判断

那么,试试吧

int lengthOfLongestSubstring(string s) {
set<char> st;
int begin = 0,ret =0;
for(int end = begin;end<s.size();++end){
if(st.count(s[end])==0){
// set中没有该字符,说明没有重复
st.emplace(s[end]);// 放进去
}else{
// set中存在,有重复字符了
ret = max(ret,end-begin);
begin = end;// 刷新窗口
}
}
return ret;
}

第一版,三个示例通过了就迫不及待地提交,结果空字符就报错了
检查一遍健壮性

随手编了一个测试用例,发现一个问题:刷新窗口后set没清空

但还是不对,这是为什么?
问题应该出在最后遍历完成后没有再记录窗口长度,也就是说如果最长无重复子串在数组最后的情况
分析一下,遍历结束后,end指针应该在数组末尾,begin指针还在窗口开头
我把end改成类变量

有一个很复杂的问题,每次出现了重复字符不是把头指针与尾指针置通,而是头指针指向被重复字符的位置去,那我改map,刷新begin到被重复元素的后一个位置,然后这样又有了新问题,容器不能简单得清空了

class Solution {
public:
int lengthOfLongestSubstring(string s) {
int ret = 0;
if(s==""){
return ret;
}
map<char,int> mp;
int begin = 0,end;
for(end = begin;end<s.size();++end){
if(mp.count(s[end])==1){
// set中存在,有重复字符了
int newLocation = mp.at(s[end])+1;
// 把begin到被重复字符之间的全踢掉
for(int i =begin;i<newLocation;++i){
mp.erase(s[i]);
}
begin = newLocation;// 刷新窗口
//放到被重复的字符位置去
}
// set中没有该字符,说明没有重复
mp.emplace(s[end],end);// 放进去
ret = max(ret,end-begin+1);
}
return ret;
}
};

这一版,能把目前的四个用例全部跑过

通过!🎉不过效率奇低

理一下思路

个人思路

初始窗口指针begin、end均为0,end始终向前走,begin不定时刷新,两个指针中间为“窗口”,即“目前无重复的最大字串”

1.end指针遍历字符串,

  • 若map中没有该字符(即当前窗口中字符无重复),将遇到的字符插入map作为键,字符的索引作为值
  • 若map中已有该字符,这时候就需要更新窗口(1. 将begin更新为被重复的元素的后一索引 2. 将原bgin到新begin之间的元素从map中删除)
  1. 为了保证数组遍历完时不会丢失最新的数组长度,于是每一步遍历都更新了结果的值

接下来考虑怎么优化算法的时间和空间效率

优化

首先,end指针的一次遍历过程是没法优化的,这是必须的

我想到了优化更新begin的过程
这样,当我遇到相同的了,就从begin(到end)开始遍历,如果指向的字符不等于现在的重复的字符,就踢掉,那么就可以又回到set

class Solution {
public:
int lengthOfLongestSubstring(string s) {
int ret = 0;
if(s==""){
return ret;
}
set<char> st;
int begin = 0,end;
for(end = begin;end<s.size();++end){
if(st.count(s[end])==1){
while(s[begin]!=s[end]){
st.erase(s[begin]);
begin++;
}
// 上面的循环结束后,begin指向了被重复的元素
begin++;
}
// set中没有该字符,说明没有重复
st.emplace(s[end]);// 放进去
ret = max(ret,end-begin+1);
}
return ret;
}
};

好像…时间提高了一点,空间没变化

继续优化,两个思路

  1. 不用STL容器能否实现?
    我之所以会用容器,主要还是为了保存和判相同,但判相同不是能用位运算?但是保存地数据结构就不好解决了,或者要不,干脆不解决,不用额外的空间呢
    ……好像不太行,遍历只会导致效率更低
  2. map能否更巧妙地实现?
    map的优点是能保存值和索引位置
    我扫描字符串,键没有,就把键(字符)值(索引)放进去,键有了,就把返回值更新为字符长度。
    不不不,思路有问题

看题解了看题解了

官方题解

滑动窗口,还是两个指针,有意思的是这里的右指针和左指针都是一直不回头地往右走的
左指针每一步的移动都会更新最长长度,并且从set中踢掉前一个元素
右指针遇到重复元素时,不再移动,左指针动就一直踢,直到把重复的元素踢掉为止

class Solution {
public:
int lengthOfLongestSubstring(string s) {
// 哈希集合,记录每个字符是否出现过
unordered_set<char> occ;
int n = s.size();
// 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
int rk = 0, ans = 0;
// 枚举左指针的位置,初始值隐性地表示为 -1
for (int i = 0; i < n; ++i) {
// 左指针向右移动一格,移除前一个字符
if (i != 0) {
occ.erase(s[i - 1]);
}
// rk的初始值为什么不设置为0?
// 当右指针没有到头且没有出现重复字符时,就不断移动右指针并将元素插入set
// 只要没有踢掉导致重复的元素,左指针就会从左往右一直踢
while (rk < n && !occ.count(s[rk])) {
occ.insert(s[rk]);
++rk;
}
// 第 i 到 rk 个字符是一个极长的无重复字符子串
// 即是左右指针的距离
ans = max(ans, rk - i);
}
return ans;
}
};

C++中字符数组名和字符串变量名

这一插一段,因为写的时候遇到了s==nullptr报错的情况,那么说明C++中字符串不是指针?不像是字符数组的数组名?
另外s==NULL也报错,那么它也不是对象?
我用了s=="",但究竟是怎么回事呢?

后记

哈哈哈,我还是很开心,毕竟是自己写出来的中等题(好吧,我看了标签提示)

2023.8.28

我还是觉得自己的代码思路好,本质上是和官解一样的,但是官解的循环结构实在是不好理解,右指针写在里面,左指针写在外面

int lengthOfLongestSubstring(string s) {
int start = 0, end,res=0;
unordered_set<char> set;
for (end = start; end < s.size(); end++) {
if (set.count(s[end])) {
while (s[start] != s[end]) {
set.erase(s[start++]);
}
start++;
}
set.emplace(s[end]);
res = max(res, end - start);
}
return res;
}

本文作者:YaosGHC

本文链接:https://www.cnblogs.com/yaocy/p/16329023.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   YaosGHC  阅读(41)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起