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

题目描述

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

题解

思路:从第一个字符开始,不断扩大子串。如果出现重复,则从重复的字符处重新开始扩大子串。

滑动窗口

  • 哈希表存储字符,用于判断是否有重复字符出现。

存在问题:算法执行时间非常长(大于600 ms)

可能原因:

  1. 哈希表被反复清空
class Solution {
    public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char, int> umap;
        int len = 0;
        int max = 0;
        for (int i = 0; i < s.length(); i++) {
            if (umap.find(s[i]) == umap.end()) {
                umap.insert({s[i], len++});
            } else {
                umap.clear();
                max = max > len ? max : len;
                i-=len; // 从失败的字符重新开始匹配。
                len = 0;
            }
        }
        max = max > len ? max : len; // 处理最长子串在结尾的情况
        return max;
    }
};

[!NOTE]

unordered_map插入的顺序是{value,index}find查找的时候,查找value值。如果找到,则返回key键;如果没有找到,则返回.end()末尾。

滑动窗口优化

此解法参考Krahets。耗费时间10 ms,速度提升近60倍。因此,哈希表的清空.clear()是一个非常耗费时间的过程。

核心思想:避免清空哈希表,保持哈希表内元素下标最新。

class Solution {
    public:
    int lengthOfLongestSubstringImproved(string s) {
        unordered_map<char, int> umap;
        int left = -1, len = 0, maxLen; // left:最长无重复字符子串的最右端。起始情况最长字符为空,所以left为-1
        for (int right = 0; right < s.size(); right++) { // right:指向被判断的字符
            if (umap.find(s[right]) != umap.end()) // 在哈希表中,被查找到
                left = max(left, umap.find(s[right])->second); // 更新左指针。反例abba,足以解释为什么要用max
            umap[s[right]] = right; // 哈希表记录。保持哈希表中的元素始终是不重复的,且坐标都是最新的,相当于窗口在滑动
            maxLen = max(len, right - left); // 更新结果
        }
        return maxLen;
    }
};

难理解点

  1. left = max(left, umap.find(s[right])->second)

举例,输入字符串abba。

当right指向第二个b时,出现重复。此时,left = -1umap.find(s[right])->second) = 2。left就直接移动到了第二个b的位置,即从重复字符开始重新匹配。注意,此时哈希表中的a的下标仍然是0。

当right指向第二个a时,出现重复。此时,left = 2umap.find(s[right])->second) = 0,即重复字符不是left指向的字符,此时left就不想要移动。

  1. umap[s[right]] = right

该方式:

  • 如果键值对不存在,则插入
  • 如果键值对存在,则修改值对应的键
  1. maxLen = max(len, right - left)

求两个范围内的个数,一般情况下需要+1,即其中一个端点。

但是,理清left和right的含义。left:最长无重复字符子串的最右端;right:被判断的字符,即最长不重复子串的在一个,相当于已经+1了。

posted @ 2024-09-30 11:49  顾子郤  阅读(6)  评论(0编辑  收藏  举报