LeetCode之算法面试之数组4之长度最小的子数组(209)、无重复字符的最长字串(3)、找到字符串中所有字母异位词(438)、最小覆盖字串(76)

————技巧点:滑动窗口

1、长度最小的子数组(209)

题目描述:

【中等题】
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

在这里插入图片描述
题目链接

思路分析

要求:求其和 ≥ s \geq s s的最小连续子数组长度

求解子数组问题时,需要先搞清楚以下几个问题

  • 连续不连续,如果是连续,有没有元素大小的情况?
    题目中是连续的子数组,且不用考虑元素的大小情况。

  • 没有解的情况需不需要考虑?
    题目中没有解的情况,默认返回为0。怎么判断没有解,可以在开始设定初始值,最后判断如果初始值不变的话,就返回0.

  • 返回的是什么?是子数组,还是子数组长度?
    如果是只返回长度值,就不用考虑多组解的情况。如果是返回最短子数组,就要考虑多组解的情况–如果是要只返回某一个解,那么这个解有什么限定?如果是要返回多个解,那么这多个解按什么顺序来返回?

题解一:暴力法:双层遍历

  • 初始化子数组的最小长度为无穷大或者len+1(最小长度不可能超过这个的)
  • 枚举数组 nums \text{nums} nums 中的每个下标作为子数组的开始下标,对于每个开始下标 i,需要找到大于或等于i 的最小下标j,(第二次遍历开始)使得从 nums [ i ] \text{nums}[i] nums[i] nums [ j ] \text{nums}[j] nums[j]的元素和大于或等于 s,并更新子数组的最小长度(此时子数组的长度是 j-i+1)。
class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        if not nums:
            return 0
        
        n = len(nums)
        ans = n + 1#初始化最小长度
        for i in range(n):
            total = 0
            for j in range(i, n):
                total += nums[j]
                if total >= s:
                    ans = min(ans, j - i + 1)#更新最小长度
                    break
        
        return 0 if ans == n + 1 else ans
  • 时间复杂度: O ( N 2 ) O(N^2) O(N2)
  • 空间复杂度: O ( 1 ) O(1) O(1)

这种方法时间复杂度为 O ( N 2 ) O(N^2) O(N2),时间太长,不建议这种方法,并且python语言实现会出现超出时间限制这一问题。

题解二:滑动窗口法

\quad \quad 在上一题解中,其中包含了大量的重复计算,对于nums[i,j]到nums[i+1,j]的和,只需要减去nums[i]即可,但是刚刚的操作是又通过一遍数组来通过。如何避免这种重复操作呢?其实,从这到那就是一个滑动的过程,将子数组nums[i,j]定义一个窗口,根据具体情况具体滑动。针对本题,具体过程如下:

  • 初始化窗口:即初始化指针l=0,r=-1

  • 初始累加和total=0

  • 初始最小长度res为其达不到的最大值len+1

  • 移动指针/滑动找到可行解并优化执行以下操作:

    • 找到可行解:如果和小于s,则右指针right不断向右移动,扩张窗口,直到sum和大于等于s,
    • 当和大于s,如果此时扩张窗口,条件依然满足,但背离“最小长度”的要求。所以选择收缩窗口优化可行解和-nums,左指针向右移动即l+=1,直到条件不再满足(这里是一个循环),也就相当于在可行解的基础上收缩窗口,使其长度更小,如果此时和不满足条件时再扩张窗口即可。
    • 如果和大于等于s,则此时窗口长度是一个可行解,更新最小长度为其最小值。
  • 如果最小长度还是初始值,则无解,返回0

  • 返回res

class Solution:
    def minSubArrayLen(self, s: int, nums: List[int]) -> int:
        #[nums[l]...nums[r]]为滑动窗口,初始其无效
        l,r=0,-1 
        #初始累加和为0
        total=0    
        #记录当前寻找到最小长度,因求最小,故初始化为最大值(不可能取到这个值)
        res=len(nums)+1 
        # 滑动窗口的左边界小于len,这是充分条件,这样右边界才可以取值
        while l<len(nums):
            '''
            对于数组问题而言,一旦用方括号来取值的话,一定要注意数组越界的问题:
            需要保证r还在取值范围内,因此在条件需要进行限定,因l限定到len-1,故r需小于len-1
            '''
            
            if r<len(nums)-1 and total<s:
                #扩大窗口
                r+=1
                total+=nums[r]
                
            else:
                total-=nums[l]
                l+=1
            if total>=s:
                res=min(res,r-l+1)
        if res==len(nums)+1: #无解
            return 0
        return res
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

2、无重复字符的最长字串(3)

题目描述:

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

在这里插入图片描述
题目链接
思路分析

其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!

如何移动?

我们只要把队列的左边的元素移出就行了,直到满足题目要求!

一直维持这样的队列,找出队列出现最长的长度时候,求出解!

【python3代码实现】

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s:
            return 0
        left=0 # 初始化左指针
        lookup=set()# 存放已经遍历过的字符
        max_len=0
        cur_len=0 # 初始化最大长度以及当前长度为0
        for i in range(len(s)):
            cur_len+=1
            while s[i] in lookup:
                lookup.remove(s[left])# 移除最左边元素
                left+=1
                cur_len-=1
            if cur_len>max_len:
                max_len=cur_len
            lookup.add(s[i])
        return max_len

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

题目描述:

【中等题】
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。

说明:

  • 字母异位词指字母相同,但排列不同的字符串。
  • 不考虑答案输出的顺序。

在这里插入图片描述
题目链接

思路分析

【python 3 代码实现】

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        res=[] # 存放结果
        needs={} # 以字典的形式存储目标字符串中各个字符的数量
        windows={} # 记录窗口中各个字符的数量
        for c in p:
            needs[c]=needs.get(c,0)+1 
        length=len(p)
        limit=len(s)
        left=right=0 # 左右指针,代表窗口的左右边界
        while right<limit:
            c=s[right]
            if c not in needs: #当遇到不需要的字符时
                windows.clear() # 将之前统计的信息清空
                left=right=right+1
            else:
                windows[c]=windows.get(c,0)+1 #统计目标字符出现的次数
                if right-left+1==length: #如果窗口长度等于目标字符串长度,则有可能是字母异位词
                    if windows==needs: # 如果窗口字典与目标字典完全一样,则是字母异位词
                        res.append(left) # 将起始索引添加到结果中
                    windows[s[left]]-=1 # 窗口函数起始索引所对应的元素的计数减一
                    left+=1 # 左指针向右移一位
                    
                right+=1 # 右指针向右移动一位
        return res
  • 时间复杂度: O ( l e n ( s ) ) O(len(s)) O(len(s))
  • 空间复杂度: O ( l e n ( p ) ) O(len(p)) O(len(p))

python dict.get()

4、最小覆盖字串(76)

题目描述:

【困难题】
给你一个字符串 S、一个字符串 T 。请你设计一种算法,可以在 O(n) 的时间复杂度内,从字符串 S 里面找出:包含 T 所有字符的最小子串。

在这里插入图片描述
题目链接

未完待续 一天一道

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值