leetcode 3.无重复字符的最长子串(滑动窗口/哈希优化)

一.题目描述


3. 无重复字符的最长子串icon-default.png?t=N7T8https://leetcode.cn/problems/longest-substring-without-repeating-characters/

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度

示例 1:

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为3。请注意,你的答案必须是 子串 的长度,"pwke"是一个子序列,不是子串。 

提示:

  • 0 <= s.length <= 5 * 10^4
  • s 由英文字母、数字、符号和空格组成

二.解题思路

1.滑动窗口

总结题目意思,在字符串中求区间值,一般都是通过滑动窗口解决。抽象案例,每一个子串的左端点和右端点都是大致不断向右移动的。所以我们采取滑动窗口加哈希表解决。

以 (a)bcabcbb 开始的最长字符串为 (abc)abcbb

以 a(b)cabcbb 开始的最长字符串为 a(bca)bcbb;

以 ab(c)abcbb 开始的最长字符串为 ab(cab)cbb;

以 abc(a)bcbb 开始的最长字符串为 abc(abc)bb

以 abca(b)cbb 开始的最长字符串为 abca(bc)bb;

以 abcab(c)bb 开始的最长字符串为 abcab(cb)b ;

以 abcabc(b)b 开始的最长字符串为 abcabc(b)b;

以 abcabcb(b) 开始的最长字符串为 abcabcb(b)

我们定义一个哈希表来记录该字符是否出现过,用left来记录子串的左端点。

遍历字符串        "pwwkew"

1 .遇见未出现过的字符,添加到哈希表中

2.遇到出现过的字符,从头部依次退出字符,直到该字符重复元素被弹出。

完整代码

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        //哈希表记录元素是否重复
        unordered_set<char> hash;
        int n = s.size();
        int maxlen = 0;
        int left = 0;
        for(int i = 0;i < n;i++){         
            //未出现,添加到hash  
            if(!hash.count(s[i])) hash.insert(s[i]);
            else {
                //已出现,弹出
                while(hash.count(s[i])) hash.erase(s[left++]);
                hash.insert(s[i]);
            }
            int len = hash.size();
            maxlen = max(maxlen, len);
        }
        return maxlen;
    }
};

 

2.优化

左指针挨个移动效率太低,哈希表不断删除,这里有许多无效循环。我们可以记录字符上一次出现的位置,如果出现过就把左指针移动到max(left, 字符上一次出现的位置+1)

好的了解了优化方向后,我们来重新梳理一下优化思路:

首先在了解题目大概意思后,我们很容易想到用一个哈希表来判断该字符是否重复。根据ASCLL表设定哈希表大小为128即可。初始化为-1表示该字符暂未出现。

vector<int> hash(128, -1);

在设定完哈希表后,又如何确定子串的长度呢。我们现在只用下标是已知的。已知下标求长度,那就很容易想到下标相减为长度。所以我们需要知道重复元素的下标,刚好使用了哈希表,可以用哈希表存储一下重复元素的下标。

hash[ s[i] ] = i

但子串长度真的只是重复元素的下标相减就行吗?

"abcbac"   

cb子串长度为第2个b的小标减去第1个b的下标        3 - 1 = 2

cba子串长度为第2个a的下标减去该子串开头前一个元素的下标既第一个b的下标4 - 1 = 3

根据上面案例的比较,我们应该取这两种情况的最小值为子串的长度。这里我们新定义sigh来确定子串的开头前一个元素的下标。

len = min(i - hash[s[i]], i -sigh);

sigh = max(hash[s[i]], sigh);

自此每个子串的长度我们就求出来了。现在只需要定义应该maxLen来在众多len中找到最大值返回即可。

全部代码

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n = s.size();
        //定义哈希表来确定元素重复
        vector<int> hash(128, -1);
        int len = 0;
        int maxlen = 0;
        //sigh标记子串的开头前一个元素的下标
        int sigh = 0;
        for(int i = 0;i < n;i++){    
            //该元素为出现过,子串长度+1 
            if(hash[s[i]] == -1) len++;
            else { 
            //元素重复,重新确定子串长度和sigh
                len = min(i - hash[s[i]], i -sigh);   
                sigh = max(hash[s[i]], sigh);  
            }
            //manlen记录长度最大值
            maxlen = maxlen >= len?maxlen:len;
            //记录每一个元素最新出现时的下标
            hash[s[i]] = i;
        } 
        return maxlen;
    }
};

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值