leetcode3. 无重复字符的最长子串

题目

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

示例 1:

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

示例 2:

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

示例 3:

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

解法1 暴力遍历

暴力遍历即对字符串中 n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)个子字符串挨个进行验证。设置i作为字符串开始的索引,设置j作为字符尾部索引,则有 i : 0 → ( l e n − 1 ) , j : i → ( l e n − 1 ) i:0\to(len -1),j:i \to (len-1) i:0(len1),j:i(len1),此外每当j+1时候都要通过遍历当前子字符串进行判断是否有重复,所以一共是3层循环。

时间复杂度 O ( n 3 ) O(n^3) O(n3)
空间复杂度 建立的子字符集大小,最长为n - 1, O ( n ) O(n) O(n)

效率太慢 100%TLE

解法2 滑动窗口

在暴力遍历中,对于 i i i开始的的字符集,我们需要 O ( n 2 ) O(n^2) O(n2)的时间复杂度来验证是否存在重复字符。
对于这一部分我们可以考虑利用hashset来优化。

滑动窗口是数组/字符串问题中常用的抽象概念。 窗口通常是在数组/字符串中由开始和结束索引定义的一系列元素的集合,即 [i, j)[i,j)(左闭,右开)。而滑动窗口是可以将两个边界向某一方向“滑动”的窗口。例如,我们将 [i, j)[i,j) 向右滑动 11 个元素,则它将变为 [i+1, j+1)[i+1,j+1)(左闭,右开)。

对于[i,j)来说(起初i = j + 1),我们判断当前hashset中是否存在该元素,若不存在,则j继续往右移动并更新最大窗口长度,若存在,则从hashset中移除i对应的元素,然后i向右移动,然后重新进行判断。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char,int> char_set;
        typedef pair<char,int> Int_Par;
        int len = 0, left = 0,right = 0;
        while(right < s.size())
        {
            if(char_set.find(s[right]) == char_set.end())
            {
                    char_set.insert(Int_Par(s[right],right));
                    right = right + 1;
                    len = max(len,right - left);
            }
            else{
                    char_set.erase(s[left]);
                    left = left + 1;
            }
        }
        return len;
    }
};

时间复杂度 O ( 2 n ) → O ( n ) O(2n) \to O(n) O(2n)O(n) 最坏情况下i和j都会遍历完数组
空间复杂度 建立的字符hashset长度,最大长度为n。 O ( m i n ( m , n ) ) O(min(m,n)) O(min(m,n))

滑动窗口优化

我们可以将两次遍历优化成一次遍历
修改hashset的结构为 key:s[i] ->value:i。这样当发现元素与已有hashset元素重复时,则可以查到到当前hashset的重复元素对应的数组索引index。然后将i修改成index + 1;

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int len = s.length();
        unordered_map<char,int> char_set;
        if(len <= 1)
            return len;
        int start = 0,end = 0,maxlen = 0;
        while(start < len && end < len)
        {
            
            auto hm = char_set.find(s[end]);
            if(hm != char_set.end())
            {
                start = max(hm->second + 1,start); //start进行跃迁时,可能会跳过一些元素,而这些元素还在hash集合里面无法处理。所以当搜索的时候我们要判断元素是否在滑动窗口内。
                char_set.erase(hm);
            }
            
            char_set.insert(make_pair(s[end],end)); // c++ hashset的insert插入相同key时候无法覆盖value,所以必须先移除再插入。来保证value永远是最靠右元素的索引
            end++;
            maxlen = max(maxlen,end - start);
        }
        return maxlen;


    }
};

时间复杂度 O ( n ) O(n) O(n)
空间复杂度 O ( m i n ( m , n ) ) O(min(m,n)) O(min(m,n)) m为字符集长度,最大不超过n.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值