leetcode-hot100-滑动窗口

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

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

示例 1:

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

示例 2:

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

同样使用双指针,lo、hi分别表示一个可行解,lo~hi表示一个子串,此外我们需要:

  1. 判断子串内无重复字符,然后求解子串长度;
  2. 更新窗口

滑动窗口,lo~hi;lo用来缩小范围,hi用于扩大范围:先固定lo左边界,扩大右边界,寻找可行解,找到之后固定hi,计算窗口内容,然后缩小lo左边界,继续寻找。

无重复字符的判断:使用一个hash表,用于记录窗口内的字符;或者是词频数组。

算法步骤:
1、“窗口”的右边界一直向右边滑动,直到发现“窗口”内的元素出现了重复,或者右边界超过了字符串的末尾,在右边界扩张的过程中,“滑动窗口”的长度在逐渐增大,此时记录下最大值;

2、只要出现了“重复”,“窗口”的右边界停止,此时左边界向右边移动,直到“滑动窗口”内没有重复的元素,跳转到第 1 步。

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        size = len(s)
        # 特判
        if size < 2:
            return size

        left, right = 0, 0
        # 滑动窗口的逻辑是尝试向右移动一位,因此,初始值是 -1
        # 设置成 128 与测试用例有关
        counter = [0 for _ in range(128)]
        # 因为自己一定是不重复的子串,在字符串非空的情况下,至少结果为 1
        res = 0
        while right < size:
            # counter[s.charAt(right)] == 0 表示在 [left, right] 这个区间里没有出现
            if counter[ord(s[right])] == 0:
                # 表示没有重复元素,right 可以加 1
                counter[ord(s[right])] += 1
                res = max(res, right - left + 1)
                right += 1
            else:
                # 如果下一个字符已经越界了,或者右边第 1 个字母是频率数组是曾经出现过的
                # 把左边从频数数组中挪掉,频数减 1
                counter[ord(s[left])] -= 1
                left += 1
        return res

# 集合判重

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        ans = 0
        hash_set = set()

        left, right = 0, 0
        while right < len(s):
            while s[right] in hash_set:
                hash_set.remove(s[left])
                left += 1
            
            hash_set.add(s[right])
            ans = max(ans, right - left + 1)
            right += 1
        
        return ans


同时,第二步中,我们可以进一步优化,将left的移动一步到位,直接移动到重复元素的下一位,如”abca“,移动到b上。因此,我们需要保存字母及其下标的对应关系。


class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        # 特判
        size = len(s)
        if size < 2:
            return size
        # key 为字符,val 记录了当前读到的字符的索引,在每轮循环的最后更新
        d = dict()
        left = 0
        # 非空的时候,结果至少是 1 ,因此初值可以设置成为 1
        res = 1
        for right in range(size):

            # d[s[right]] >= left,表示重复出现在滑动窗口内
            # d[s[right]] < left 表示重复出现在滑动窗口之外,之前肯定计算过了
            if s[right] in d and d[s[right]] >= left:
                # 下一个不重复的子串至少在之前重复的那个位置之后
                # 【特别注意】快在这个地方,左边界直接跳到之前重复的那个位置之后
                left = d[s[right]] + 1

            # 此时滑动窗口内一定没有重复元素
            res = max(res, right - left + 1)
            # 无论如何都更新位置
            d[s[right]] = right
        return res

438. 找到字符串中所有字母异位词

给定两个字符串 sp,找到 s 中所有 p异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:

输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。

一种思路:我们在s串中找到所有长度等于len§的子串,判断子串的排序结果和p串的排序结果是否相同,如果相同说明找到一个解,记录下标。

另一种思路: 采用滑动窗口。我们先记录p串,保存词频数组。然后使用滑动窗口遍历s串,找到所有符合条件的异位词。对于s串,我们也可以使用一个词频数组,判断两个数组相等来确定是否找到异位词。我们的滑动窗口保持长度为len§。

class Solution:
    def findAnagrams(self, s: str, p: str):
        result = []
        scount = [0] * 26  # 字母计数数组,用于s
        pcount = [0] * 26  # 字母计数数组,用于p

        # 计算p中每个字母的频率
        for c in p:
            pcount[ord(c) - ord('a')] += 1

        ssize = len(s)
        psize = len(p)

        # 如果s的长度小于p,直接返回空结果
        if ssize < psize:
            return result

        # 遍历字符串s
        for i in range(ssize):
            # 更新当前字符在s中的计数
            scount[ord(s[i]) - ord('a')] += 1
            # 如果窗口大小超过了p的长度,移动窗口
            if i >= psize:
                scount[ord(s[i - psize]) - ord('a')] -= 1
            # 如果两个计数数组相等,则找到了一个字母异位词的起始索引
            if scount == pcount:
                result.append(i - psize + 1)

        return result


class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        vector<int> result;
        vector<int> scount(26), pcount(26);
        for (char c: p) ++pcount[c-'a'];
        int ssize = s.size(), psize = p.size();
        if (ssize < psize) return result;
        for (int i=0; i<ssize; ++i) {
            scount[s[i]-'a']++;
            if (i>=psize) scount[s[i-psize]-'a']--;
            if (scount == pcount)
                result.push_back(i-psize+1);
        }
        return result;
    }
};

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值