2021-02-09

2021-02-09 Leetcode每日刷题

题目

给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A的这个连续、不一定独立的子数组为好子数组。
例如,[1,2,3,1,2] 中有 3 个不同的整数:1,2,以及 3。
返回 A 中好子数组的数目。

示例一:

输入:A = [1,2,1,2,3], K = 2
输出:7
解释:恰好由 2 个不同整数组成的子数组:[1,2], [2,1], [1,2], [2,3], [1,2,1], [2,1,2], [1,2,1,2].

我的思路
首先考虑暴力破解,时间复杂度为n^2,于是提交的时候在测试用例挂掉了。一看题目就是滑动窗口了,但是自己理解没跟上,以为写的是滑动窗口实际还是暴力解法…就不放出来了。下面主要参考负雪明烛的思路和官方思路。
这里补一段负雪明烛写的滑动窗口求解最长区间长度问题的一般思路:

def findSubstring(s):
    N = len(s) # 数组/字符串长度
    left, right = 0, 0 # 双指针,表示当前遍历的区间[left, right],闭区间
    counter = collections.Counter() # 用于统计 子数组/子区间 是否有效
    res = 0 # 保存最大的满足题目要求的 子数组/子串 长度
    while right < N: # 当右边的指针没有搜索到 数组/字符串 的结尾
        counter[s[right]] += 1 # 增加当前右边指针的数字/字符的计数
        while 区间[left, right]不符合题意:# 此时需要一直移动左指针,直至找到一个符合题意的区间
            counter[s[left]] -= 1 # 移动左指针前需要从counter中减少left位置字符的计数
            left += 1 # 真正的移动左指针,注意不能跟上面一行代码写反
        # 到 while 结束时,我们找到了一个符合题意要求的 子数组/子串
        res = max(res, right - left + 1) # 需要更新结果
        right += 1 # 移动右指针,去探索新的区间
    return res

之所以叫滑动窗口是因为left和right的移动都是有判断条件的,以右指针作为驱动,拖着左指针向前走。右指针每次只移动一步,而左指针在内部 while 循环中每次可能移动多步。右指针是主动前移,探索未知的新区域;左指针是被迫移动,负责寻找满足题意的区间。

模板的整体思想是:

  • 定义两个指针 left 和 right 分别指向区间的开头和结尾,注意是闭区间;定义 counter 用来统计该区间内的各个字符出现次数
  • 第一重 while 循环是为了判断 right 指针的位置是否超出了数组边界;当 right 每次到了新位置,需要增加 right指针的计数;
  • 第二重 while 循环是让 left 指针向右移动到 [left, right] 区间符合题意的位置;当 left 每次移动前,需要减少 left 指针的计数
  • 在第二重 while 循环之后,成功找到了一个符合题意的 [left, right] 区间,题目要求最大的区间长度,因此更新 res 为 max(res, 当前区间的长度)
  • right 指针每次向右移动一步,开始探索新的区间。

参考思路
思路一:
恰好由K个不同整数组成的子数组=最多由K个不同整数组成的子数组-最多由K-1个不同整数组成的子数组。

class Solution:
    def getmax(self,A,K):
        l = len(A)
        left = 0
        right = 0
        counter = collections.Counter()
        nums = 0
        res = 0
        while right<l:
            if counter[A[right]] == 0:  #当right指向的数第一次出现那么区间内不同数字数量+1
                nums+=1
            counter[A[right]] += 1      #right指向的数出现的次数+1
            while nums > K:            #当区间内不同数字数量大于K的时候,需要移动left,并且在移动left之前把left指向的数出现的次数-1
                counter[A[left]]-=1
                if counter[A[left]] == 0:     #当指向数的出现次数-1后为0时,说明区间内将不再存在这个数,那么不同数字的数量自然-1
                    nums-=1
                left+=1
                
            res+=right-left+1       #求最大:res = max(res,right-left+1); 求所有的子集:res+=right-left+1
            right+=1
        return res

    def subarraysWithKDistinct(self, A: List[int], K: int) -> int:
        res1 = self.getmax(A,K)
        res2 = self.getmax(A,K-1)
        return res1-res2

提交结果:
2021-02-09_第1张图片
思路二:
两个滑动窗口嵌套:可以理解为在原本[left,right]基础上把left换成滑动窗口[left1,left2],其中left1保证[left1,right]中最多含有K个不同数字,left2保证[left2,right]中最多含有K-1个不同数字。所以本质上和思路一是一样的。
代码明天上午贴。累了,睡觉!

Reference
https://leetcode-cn.com/problems/subarrays-with-k-different-integers/solution/cong-zui-jian-dan-de-wen-ti-yi-bu-bu-tuo-7f4v/

你可能感兴趣的:(leetcode)