【力扣】无重复字符的最长子串,滑动窗口+哈希集合+优化

无重复字符的最长子串原题地址

方法一:滑动窗口

考虑用2个指针来维护子串,使得这条子串没有重复字符。

i和j表示下标,[i,j]表示子串,长度为j-i+1。我们可以用i遍历字符串的所有字符,对于每一个i,都尽可能地让j往右滑动,使得[i,j]为无重复字符的子串。此时,每一个i都有唯一对应的j,即j=r(i)。本题只需要求得j-i+1的最大值即可。

力扣的官方题解中给出了一个例子,可以很好地呈现这种思路。

【力扣】无重复字符的最长子串,滑动窗口+哈希集合+优化_第1张图片

j=r(i)这个函数是不减的。即对于任意i_1<i_2j_1=r(i_1)j_2=r(i_2),我们有j_1\leq j_2恒成立。这个结论可以直观地想象到,显然[i_1,j_1]没有重复字符,那么[i_2,j_1]自然也没有重复字符,所以j_2一定在j_1的右边。

那么问题就转换为,如何找到每个i对应的j呢?我们只需要维护一个哈希表,把当前[i,j]的所有字符都存进去。当i向右移动时,就移除最左边的字符;当j向右移动时,就添加最右边的字符。这样,我们可以时刻知道最右边的字符是否在[i,j]的范围内出现过,若出现重复字符,则此时的[i,j]就是当前i对应的无重复字符的最长子串。

注意,j必须初始化为-1,这是因为下标为0的字符也要存储进哈希表中。

// 方法一:滑动窗口+哈希集合set
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_set us;
        int ans = 0;

        // 滑动窗口
        // i表示左边界,j表示右边界,即[i,j]
        int j = -1; // 右指针移动时要插入,设置成-1会插入第一个字符
        int n = s.size();
        for (int i = 0; i < n; ++i)
        {
            // 左指针向右移动一格,要去掉左边的一个元素
            if (i)
            {
                us.erase(s[i - 1]);
            }

            // 右指针向右移动一格,如果该字符没有重复,就插入
            while (j + 1 < n && !us.count(s[j + 1]))
            {
                ++j;
                us.insert(s[j]);
            }

            // 窗口长度为j-i+1
            ans = max(ans, j - i + 1);
        }

        return ans;
    }
};

方法二:双指针,对方法一的优化

方法一是固定左指针,让右指针动,最坏情况下,2个指针都要遍历一遍字符串。

我们可以考虑把每个字符当前最后一次出现的下标也存下来,这样左指针就不用一步一步动了,可以直接跳跃到某个位置。

具体的操作如下:仍然用i和j表示区间端点的坐标,这次采用(i,j]来表示子串,子串长度为j-i。使用j遍历字符串,对于每一个j,先在哈希表中查找当前字符s[j]有没有出现过,如果没有出现过,则当前子串(i,j]是合法的;若出现过,则需要更新i到当前字符最后一次出现的位置(如abcb,当j指向第2个b,i指向a前面,此时的子串abcb非法,需要把i更新为指向第1个b,此时对应的子串cb合法)。最后记得把当前字符的位置存进去。

更新i的位置时,应取(i和当前字符最后一次出现的下标m)的较大值,而不是直接取m。这是因为,若m

// 方法二:优化方法一,滑动窗口+哈希集合map
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        // 存储元素及其最后一次出现的下标
        unordered_map um;
        int ans = 0;

        int n = s.size();
        int i = -1;
        for (int j = 0; j < n; ++j)
        {
            // 如果该字符出现过,且在区间范围内,需要更新左端点
            auto it = um.find(s[j]);
            if (it != um.end())
            {
                //i = it->second; // err, 反例:abba
                i = max(i, it->second);
            }

            // 此时为右端点当前最后一次出现
            um[s[j]] = j;
            // 无重复区间长度为j-i
            ans = max(ans, j - i);
        }

        return ans;
    }
};

你可能感兴趣的:(leetcode,哈希算法,算法)