三、滑动窗口问题

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 由英文字母、数字、符号和空格组成

题目思路

对于这道题,要求是找到最长连续的、不包含重复字符的字符子串。

经过观察思考我们可以得知,可以通过滑动窗口的方法来寻找这样的答案。

这里,子串的条件是不能包含重复的字符,因此首先在遍历时,我们可以定义一个哈希表来保存对应字符所处位置,方便查看当前字符是否在子串中。

同时,定义leftright指针,分别作为滑动窗口的左右两端,因此子串长度就是right - left

right从左到右遍历时,对于s[right]

  • 如果该字符未曾出现、或出现的位置在当前子串左边,那么该字符可以作为
  • 否则,就对left指针进行更新——即更新到对应已重复字符的下一个位置,将重复排除掉;
  • 最后,每轮循环都需要记录或刷新当前字符的位置;

算法代码

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        letter_index_map = {}
        left, right = 0, 0
        max_length = 0
        while right < len(s):
            curr_letter_index = letter_index_map.get(s[right])
            # 说明当前字符要么从未出现、要么在当前子序列之前出现
            if curr_letter_index is None or curr_letter_index < left:
                max_length = max(max_length, right - left + 1)
            else:
                left = curr_letter_index + 1
            # 记录/刷新当前字符所在位置
            letter_index_map[s[right]] = right
            right += 1
        return max_length

438、找到字符串中所有字母异位词(中等)

题目描述

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

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

示例 1:

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

示例 2:

输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的异位词。

提示:

  • 1 <= s.length, p.length <= 3 * 104
  • s 和 p 仅包含小写字母

题目思路

所谓的异位词,在哈希问题中已有说明: 49、字母异位词分组
在该问题中,既可以直接对单词进行排序来判断。

在这道题中,这里如果直接去排序的话,需要排序的次数就太多了,例如如果s长度是10,p长度是3,那么至少要排序7+1次,更不用说s和p的长度可能会非常大。

因此,另一种方法则是字母计数,只需要使用ASCII码作为索引构造哈希表,然后统计字母出现次数,最后对比两个哈希表是否相同,就可以知道两个单词是否是字母异位词了。

在这道题中,本质上就是以p的长度划定一个滑动窗口,每向右移动一位时:

  • 窗口最左侧字符去掉,因此该字符的计数-1;
  • 窗口最右侧字符新增,因此该字符的计数+1;
  • 如果此时的s_counterp_counter是否相同,就将对应索引加入结果中;

需要注意的是,在滑动前,我们自然是要有已经构造好的窗口,因此定义两个字母哈希表s_counterp_counter

  • s_counter,记录滑动过程中,s字符子串字符出现次数;
  • p_counter,不变量,记录p的字符出现次数,相当于模板;

同时,因为所谓遍历,本质上就是开始滑动窗口,因此构造完s_counterp_counter后,需要首先做一下判断,看下第一个滑动区间是否满足条件。

算法代码

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        s_len, p_len = len(s), len(p)
        if s_len < p_len:
            return []

        s_counter = [0] * 26
        p_counter = [0] * 26

        total_anagrams_idx = []
        for i in range(p_len):
            s_counter[ord(s[i]) - ord('a')] += 1
            p_counter[ord(p[i]) - ord('a')] += 1

        # 第一个滑动区间单独判断
        if s_counter == p_counter:
            total_anagrams_idx.append(0)

        for i in range(s_len - p_len):
            # 滑动一位,去掉左侧字符,新增右侧字符
            s_counter[ord(s[i]) - ord('a')] -= 1
            s_counter[ord(s[i + p_len]) - ord('a')] += 1

            if s_counter == p_counter:
                total_anagrams_idx.append(i + 1)

        return total_anagrams_idx

补充说明

所谓滑动窗口法,又称为“寸取法”,一般用来解决查找满足依一定条件的连续区间的特殊性质(长度等) 等一类问题

由于区间是连续的,因此当整个区间发生变化时,可以通过对旧有的计算结果对搜索空间的剪枝(一般就是滑动窗口最左侧滑出的部分),从而减少了重复计算、降低了时间复杂度、避免了暴力的搜索。

往往类似于“请找到满足xx条件的最x区间/子串/子数组”这一类问题都可以使用滑动窗口法来进行解决。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值