[Leetcode]930.和相同的二元子数组+992.K个不同整数的子数组 关键词:[子数组][滑窗]

Leetcode 992

在这里插入图片描述
1 <= nums.length <= 20000
1 <= nums[i], k <= nums.length

方法一:滑窗右端每次+1,左端来回滑动

这道题初步看上去像滑窗。滑窗解决的问题是“最长”,比如找“无重复字符的最长子串”、“特定排列的子串”。通用方法就是滑窗右侧尽可能向右,直到滑窗内元素的个数不满足要求那么滑窗左侧右移。

本题考虑的不是“最多K个不同整数”,而是“恰好K个不同整数”。

我的办法(方法一)是先用滑窗找到“最多K个不同整数”的每个左右边界,然后对于这其中的每一个右边界,左边界“尝试右移”,找到全部合适的左边界。比如序列12123,对于第二个2来说,“最多2个不同整数”,就是1212,满足条件的左边界有3个,[1]212、[2]12、[1]2.

具体到代码实现,对于每一个右边界(即将加入滑窗)的值来说:

  1. 如果这个值曾经在滑窗中出现过,则把该值加入滑窗之后,滑窗内仍然是恰好K种数,那么pl“尝试右移”:cnt数组相应减少,直到遇到第一个将cnt的某个非零值减到0位置的位置tmp_pl,那么tmp-pl就是此时右边界对应所有左边界的个数。然后把pl~tmp_pl区间内的数再加回cnt数组(恢复)。
  2. 如果这个值没有在滑窗中出现,那么把该值加入滑窗之后,滑窗内就有K+1种数了,此时pl必须右移。右移到合适位置后,再进行1中的“尝试右移”操作。
class Solution {
    int cnt[20010] = {0};
    int ans = 0;

public:
    int subarraysWithKDistinct(vector<int>& nums, int k) {
        int pl = 0, pr = 0, k2 = 0;

        // 先找k个不同的,定下初始滑窗位置 左闭右开
        for (; pr < nums.size() && k2 < k; pr++)
            if (++cnt[nums[pr]] == 1) k2++;
        
        if (k2 < k) return 0;
        ans++;

        // 开始滑动
        while (pr < nums.size()) {
            // 先尝试pl右移
            int tmp_pl = pl;
            while (cnt[nums[tmp_pl]] > 1) {
                ans++;
                cnt[nums[tmp_pl]]--; tmp_pl++;
            }
            // 恢复
            while (tmp_pl > pl) {
                tmp_pl--;
                cnt[nums[tmp_pl]]++;
            }

            // if 下一个数是旧数,加入
            if (cnt[nums[pr]] > 0) {
                cnt[nums[pr]]++; pr++;
                ans++;
            }
            // if 下一个数是新数,pl右移至窗内种类数-1
            else {
                cnt[nums[pr]]++; pr++;
                do {
                    cnt[nums[pl]]--;
                    pl++;
                } while (cnt[nums[pl - 1]] > 0);
                ans++;
            }
        }

        // 尝试pl右移
        int tmp_pl = pl;
        while (cnt[nums[tmp_pl]] > 1) {
            ans++;
            cnt[nums[tmp_pl]]--;
            tmp_pl++;
        }
        return ans;
    }
};

时间复杂度:最坏是Onn,但是实际还不错。
在这里插入图片描述

方法二:(最多K种的子串数) - (最多K-1种的子串数) = 恰好K种

这个方法是官方题解法。

还是12123,且K=2这个例子,对于1212来说,有3个左边界可以满足“恰好K种”,这个3是怎么算出来的呢?最多K-1种的左边界是1个,一共4个潜在的左边界,4-1=3.

(最多K种的子串数) - (最多K-1种的子串数) = 恰好K种

时间复杂度On
代码和下面这道题是几乎一模一样的

Leetcode 930

在这里插入图片描述

方法一:(最多和为goal的子串数) - (最多和为goal-1的子串数) = 恰好和为goal

就是992题的方法二

// 本题跟992非常类似,可以用一模一样的方法。O(n)
class Solution {
    int f(vector<int>& nums, int goal) {
        if (goal < 0) return 0;
        int cnt = 0; // 滑窗内1的个数。因为数组内只有0或1,所以用一个变量就能存滑窗内的值。
        int pl = 0, pr = 0, ans = 0;
        while (pr < nums.size()) {
            cnt += nums[pr];
            pr++;

            // 看是否需要缩小滑窗范围
            while (cnt > goal) {
                cnt -= nums[pl];
                pl++;
            }
            ans += (pr - pl);
        }
        return ans;
    }

public:
    int numSubarraysWithSum(vector<int>& nums, int goal) {
        return f(nums, goal) - f(nums, goal - 1);
    }
};

在这里插入图片描述

方法二:解释一下抽象的官解

这个官解抽象得令人发指,下面我来解释一下,此题跟哈希表有啥关系、跟前缀和有啥关系。

在这里插入图片描述
在这里插入图片描述
还是回来考虑,对于每一个固定的右边界。符合要求的左边界有几个。比如样例的序列10101——
对于右边界是下标2,符合要求的子序列是[101]01——1种
对于右边界是下标3,符合要求的子序列是[1010]1——1种
而对于右边界是下标4,符合要求的子序列是1[0101] 10[101]——2种
总共是4种.

拿右边界是下标4的举例子,这个2种怎么来的?从下标0到下标4,前缀和为3,也就是从开始到现在,已经看见3个1了,我的目标是看见2个1,那么减去前缀和是1的情况就可以了。

所以官解里用了map<key, value> 维护前缀和是key的种类数有value个。
对于每一个右边界,看到它位置遇见的1比goal多了几个,减去map中记录的值即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值