leetcode. 3 (无重复字符的最长子串)

Q:给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串 。

先用我自己的方法写了一下这道题:
主要是要考虑一些细节的地方:
先举个例子:shsiasyehdbxjxjsg
它可以分成以下几段:
sh
sia
ia
syehdbxj

xjsg
1.我本来想的是如果遇到前面出现过的字符,就在这断掉,然后重新开始新的字符,比如sia,syehdbxj,这样最长的子串长度是 syehdbxj.size()。但是我没有考虑如果前面加上 ia,也符合无重复子串的长度,这样最长子串长度就是iasyehdbxj.size()。这个是需要注意的一个地方。PS:这里要感谢晚上11.30还在陪我刷题的男朋友给我找出的问题。
2.如果遇到连续重复的字符,和遇到之前出现过的字符处理是不同的,所以我放在else if{} else{}里,分两段写了。
3.最后我把所有分出来的几段字符长度都放在一个vector数组里,本来还想写个从里面找最大值的函数。后来灵机一动直接用sort排序,然后最后一个数肯定就是最大值了。

代码如下:

class Solution {
public:
    int lengthOfLongestSubstring(string s) 
    {
        if (s.empty())return 0;
        if (s.size() == 1)return 1;//只有一个字符就直接返回1
        char temp = s[0];
        string s1;
        vector<int>result;//记录不同的子串的长度。其实也可以用两个计数器,一个用来计数,另一个用来存放之前的个数,然后两个比较,留下大的那个。不知道会不会比创建vector快一点。明天试一下。
        int count = 1, last;
        s1.push_back(s[0]);
        for (int i = 1; i<s.size(); i++)
        {
            if ((temp != s[i]) && (s1.find(s[i]) == string::npos))//如果前面没重复的
            {
                count++;
                s1.push_back(s[i]);
                temp = s[i];
            }
            else if (temp == s[i])//如果出现相连的重复字符
            {
                result.push_back(count);
                count=0; s1.clear();
                temp = s[i];
                s1.push_back(s[i]); count++;
            }
            else//如果前面出现过这个字符,而且也不是重复的相连字符。
            {
                result.push_back(count);
                int m = s1.find(s[i]);

                count=count-m-1; s1.erase(s1.begin(),s1.begin()+m+1);
                temp = s[i];
                s1.push_back(s[i]); count++;
            }
        }
        result.push_back(count);
        sort(result.begin(), result.end());//对记录长度的数组从小到大排序
        last = result[result.size() - 1];//最后的一个数肯定是最大的。
        return last;     
    }             
};

在这里插入图片描述
尝试了上面代码里备注所说的计数问题,看看会不会对运行时间产生影响。加了一句:

count2=count>count2?count:count2;//count2 用来保存子串长度的最大值。

最后时间并没有什么改变,说明了主要的时间耗费,可能还是在for循环和各种比较上。

看一下官方给出的解决方法:
1.暴力法
列举出来所有的子字符串,然后通过一个bool函数检查每个子字符串中有没有重复的字符,如果没有,就返回true ,再找到其中最大的数即可。

编程中用到set容器: set容器和map容器类似,但是set容器只存储键,map容器是键-值对。比如存20个1,map里存的是<1,20>,set里存的是1。所以不管存多少个同样的数字,set里只有一个数。
因此,set容器适合用来查看某个数是否存在。set.find()返回的是某个数的位置。set.count返回的是某个数的是否存在,存在就return 1 。
代码如下:在986 / 987 这个例子超时了。代码先放在这,就是用的官方题解的Java版改的,看评论里也有好多人超时了。

class Solution {
public:
    bool allUnique(string s,int begin,int end)
    {
        set<char>set1;
        for(int i=begin;i<end;i++){
            if(!set1.count(s[i]))
                set1.insert(s[i]);
            else 
                return false;
        }
        return true;
    }
    int lengthOfLongestSubstring(string s) 
    {
        if (s.empty())return 0;
        if (s.size() == 1)return 1;
        int result=0;
        for(int i=0;i<s.size()-1;i++){
            for(int j=i+1;j<=s.size();j++){
                if(allUnique(s,i,j))
                    result=max(result,j-i);    
            }
        }
    return result;            
    }             
};

2.滑动窗口:
下面是官方题解:

在暴力法中,我们会反复检查一个子字符串是否含有有重复的字符,但这是没有必要的。如果从索引 i 到 j - 1 之间的子字符串 Sij 已经被检查为没有重复字符。我们只需要检查 s[j]s[j] 对应的字符是否已经存在于子字符串 Sij 中。滑动窗口是数组/字符串问题中常用的抽象概念。
窗口通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即 [i, j)(左闭,右开)。而滑动窗口是可以将两个边界向某一方向“滑动”的窗口。例如,我们将 [i, j)向右滑动 1 个元素,则它将变为 [i+1, j+1)(左闭,右开)。
回到我们的问题,我们使用 HashSet 将字符存储在当前窗口 [i, j)[i,j)(最初 j = i)中。 然后我们向右侧滑动索引 j,如果它不在 HashSet 中,我们会继续滑动 j。直到 s[j] 已经存在于 HashSet 中。此时,我们找到的没有重复字符的最长子字符串将会以索引 i 开头。如果我们对所有的 i 这样做,就可以得到答案。

在实际编程中,发现一个小问题,就是如果用官方题解提供的set模板,向s1中插入字符,会自动按照字符的ASCII码进行排序,这样就打乱了插入的顺序:如测试序列 “ynyo”: n的ASCII码是110 ,y的ASCII码是121,本来想插入的顺序是y,n ,但是insert之后就按照ASCII码大小顺序排序,s1中存储就变成了 n,y。具体的解决办法没想到,就换用了string类型。

class Solution {
public:
    int lengthOfLongestSubstring(string s) 
    {
        int i=0,j=0,last=0;
        string s1;
        //set<char>s1;
        while(i<s.size()&&j<s.size()){
            if(s1.find(s[j]) == string::npos){
                s1.push_back(s[j]);
               // s1.insert(s[j]);
                j++;
                last=max(last,j-i);
            }
            else{
                s1.erase(s1.begin());
                i++;        
            }
        }
       return last; 
    }             
};

其实感觉这个思想和我之前自己写的那个方法的思想类似,运行时间也是一样的。

3.优化滑动窗口
原来的方法中,i 是一个一个的蹦过去,现在就直接让 i 跳到出现重复字符的位置即可。

当我们知道该字符集比较小的时侯,我们可以用一个整数数组作为直接访问表来替换 Map。
常用的表如下所示:

int [26] 用于字母 ‘a’ - ‘z’或 ‘A’ - ‘Z’
int [128] 用于ASCII码
int [256] 用于扩展ASCII码

代码如下:

class Solution {
public:
    int lengthOfLongestSubstring(string s) 
    {
        vector<int>v(128,0);
        int t=0;int ans=0;
        for(int i=0;i<s.length();i++)
        {
            t=max(t,v[s[i]]);
            ans=max(ans,i-t+1);
            v[s[i]]=i+1;
        }
        return ans;
    }             
};

在这里插入图片描述
运行并不是很稳定,有时候12ms,有时候20ms。我也不知道为啥

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值