【Leetcode】992. Subarrays with K Different Integers

1

解法

解法一:LRU Cache

考虑以A[j]为结尾的连续数组中有k个不同的数字有多少种情况,我们把数组A看成请求序列,那么第j个请求时LRU cache的状态可以表示成一个链表,其中第k个数字的下标 i k i_k ik处就是往回追溯的第k个unique的数字,所以 i = i k i=i_k i=ik是使得[i,j]中有k个不同数字的最大的那个左边界。
左边界还可以再继续减小,直到遇到第k+1个不同数字,也就是 i k + 1 i_{k+1} ik+1处的那个数字,这时 [ i k + 1 , j ] [i_{k+1},j] [ik+1,j]之间就有k+1个数字了。
所以以A[j]为结尾的连续数组中有k个不同的数字的情况共有: i k − i k + 1 i_k-i_{k+1} ikik+1种。
因此我们可以维护一个大小为K+1的LRU cache

class LinkNode(object):
    def __init__(self, x,y):
        self.val = x
        self.idx = y
        self.prev = None
        self.next = None

class Solution(object):
    def subarraysWithKDistinct(self, A, K):
        """
        :type A: List[int]
        :type K: int
        :rtype: int
        """
        n = len(A)
        back,head = LinkNode(0,-1),LinkNode(n+1,n)
        head.next = back
        back.prev = head
        cache = {0:back}
        ans = 0
        K += 1
        for i,a in enumerate(A):
            if a in cache:
                cache[a].prev.next = cache[a].next
                if cache[a].next:
                    cache[a].next.prev = cache[a].prev
                else:
                    back = cache[a].prev
                cache[a].prev = head
                cache[a].next = head.next
                head.next.prev = cache[a]
                head.next = cache[a]
                cache[a].idx = i
            else:
                cache[a] = LinkNode(a,i)
                cache[a].prev = head
                cache[a].next = head.next
                head.next.prev = cache[a]
                head.next = cache[a]
                if len(cache)>K:
                    back.prev.next = None
                    cache.pop(back.val)
                    back = back.prev
            if len(cache)==K:
                ans += back.prev.idx-back.idx
        return ans

解法二:滑动窗口

看了一下网站上给的答案,基本原理跟解法一相似,也需要维护两个值 l k l_k lk l k − 1 l_{k-1} lk1,分别是使得 [ i , j ] [i,j] [i,j]里有k个不同的数的最小的左边界。
但是采用的是类似尺取法的思想,那就是随着j增加, i k i_k ik i k + 1 i_{k+1} ik+1是递增的。

class Counter(object):
    def __init__(self):
        self.cnt = {}
        self.num = 0
        
    def add(self,a):
        self.cnt[a] = self.cnt.get(a,0)+1
        if self.cnt[a]==1:
            self.num += 1
        
    def remove(self,a):
        self.cnt[a] -= 1
        if self.cnt[a] == 0:
            self.num -= 1

class Solution(object):
    def subarraysWithKDistinct(self, A, K):
        """
        :type A: List[int]
        :type K: int
        :rtype: int
        """
        n = len(A)
        ans = lk = lk1 = 0
        k = Counter()
        k1 = Counter()
        for i,a in enumerate(A):
            k.add(a)
            k1.add(a)
            while lk<=i and k.num>K:
                k.remove(A[lk])
                lk += 1
            while lk1<=i and k1.num>=K:
                k1.remove(A[lk1])
                lk1 += 1
            ans += lk1-lk
        return ans

效率没有前者高

解法三:优化解法二

参考评论区@lantusky 的答案

解法二的问题是维护两个Counter很麻烦
思路见注释:

class Solution(object):
    def subarraysWithKDistinct(self, A, K):
        """
        :type A: List[int]
        :type K: int
        :rtype: int
        """
        # former是使得有K个不同数字的最小边界
        # latter是最大边界
        ans = former = latter = 0
        # freq记录了滑动窗口[latter,j]里每个数字的出现频率
        freq = {}
        for a in A:
            freq[a] = freq.get(a,0) + 1
            # 当窗口右边扩大一位之后引入了一个新的数字
            if len(freq)==K+1:
                # 窗口过大,需要增大左边界以减小窗口
                # 由于latter是最大的左边界,所以freq[A[latter]]一定为1,否则矛盾
                # 所以freq[A[latter]]-1一定为0,那么减小窗口就相当于把它pop掉
                freq.pop(A[latter])
                latter += 1
                # pop完之后窗口[latter+1,j]里一定只有K个不同数字了
                # 所以latter+1就是最小的左边界
                former = latter
            # 这里加一句判断是为了把窗口里unique数字小于K的情况去掉
            if len(freq)==K:
                # 求latter的准确值
                while freq[A[latter]]>1:
                    freq[A[latter]] -= 1
                    latter += 1
                ans += latter-former +1
        return ans
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值