Leetcode 992. Subarrays with K Different Integers (DP + Two pointer)

本文探讨了一种特殊的子串问题,即寻找数组中满足特定条件(含有K个不同元素)的子串数量。文章采用双指针和动态规划相结合的方法,详细阐述了解决方案的设计思路与实现细节,包括关键变量定义、状态转移方程及算法复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

  • 给你一个长为NNN的数组AAA,返回AAA中满足条件的子串个数
  • 条件是:子串中恰好有KKK个不同的元素
  • 数据范围:N≤20000N \le 20000N20000

思路

  • 这种子串的问题一般是用two pointer或者DP来解决,这个题比较有意思的是两种思路都要用到
  • 先根据DP的想法,我们考虑子问题,设前iii个元素这样的子串有F(i)F(i)F(i)个,必须以iii结尾的子串有f(i)f(i)f(i)
  • 这样有一个很简单的结论:F(i)=F(i−1)+f(i)F(i) = F(i-1) + f(i)F(i)=F(i1)+f(i)
  • 那么我们的问题的关键就是用f(i−1)f(i-1)f(i1)的知识帮助我们求出f(i)f(i)f(i)了,很显然这两个部分关联性非常强,举个例子比如A(i)=A(i−1)A(i) = A(i-1)A(i)=A(i1)的情况,那么有f(i)=f(i−1)f(i) = f(i-1)f(i)=f(i1)
  • 这里我们需要一些其它的辅助信息了,首先是两个指针p1(i)p_1(i)p1(i)p2(i)p_2(i)p2(i),我们让他们表示以iii结尾的可行子串的第一个和最后一个开头位置的索引,不难发现,如果存在以iii结尾的可行子串,那么所有的iii结尾可行子串的开头位置应该都在p1(i)p_1(i)p1(i)p2(i)p_2(i)p2(i)之间,而且它们之间的位置作为头也都是可行的,即f(i)=p2(i)−p1(i)+1f(i) = p_2(i) - p_1(i) + 1f(i)=p2(i)p1(i)+1
  • 另外,我们还需要记录一下映射map<int, int>记为mim_imi,他记录以iii结尾的子串里,包含的数值,以及数值对应出现的位置,要求这个位置是在iii之前该数值最后一次出现的位置
  • 那么我们接下来就是用p1(i)p_1(i)p1(i)p2(i)p_2(i)p2(i)mim_imi来更新p1(i+1)p_1(i+1)p1(i+1)p2(i+1)p_2(i+1)p2(i+1)mi+1m_{i+1}mi+1
  • 加入A(i+1)A(i+1)A(i+1)出现在了mim_imi中,那么其实p1(i)p_1(i)p1(i)p2(i)p_2(i)p2(i)之间的位置做开头一定都可行,只是如果p2(i)=A(i+1)p_2(i) = A(i+1)p2(i)=A(i+1)时,我们会有更多可行解;我们首先更新m(A(i+1))=i+1m(A(i+1)) = i+1m(A(i+1))=i+1并赋值给mi+1m_{i+1}mi+1,需要通过遍历更新p2p_2p2,我们找到第一个位置jjj,保证A(j)==mi+1(A(j))A(j) == m_{i+1}(A(j))A(j)==mi+1(A(j)),领p2(i+1)=jp_2(i+1)=jp2(i+1)=j
  • 如果A(i+1)A(i+1)A(i+1)未出现在mim_imi中,那么p1(i)p_1(i)p1(i)p2(i)p_2(i)p2(i)之间的位置做开头就会多一个元素,不难发现这个元素就是A(p2(i))A(p_2(i))A(p2(i)),因此我们令p1(i+1)=p2(i)+1p_1(i+1) = p_2(i) + 1p1(i+1)=p2(i)+1p1(i+1)p_1(i+1)p1(i+1)的更新则和上一个条件分支基本一样,然后更新mim_imi即可
  • 我们可以发现p1p_1p1p2p_2p2的更新都是单调递增的,因此每个位置至多被遍历一次,所以总体复杂度是O(N)O(N)O(N)
class Solution {
public:
    int subarraysWithKDistinct(vector<int>& A, int K) {
        unordered_map<int, int> mapp;
        int pre = 0, suf = 0, now = 0;
        int ans = 0;
        for (; now < A.size(); now++){
            mapp[A[now]] = now;
            if (mapp.size() == K){
                break;
            }
        }
        for (int i = now; i < A.size(); i++){
            if (mapp.find(A[i]) != mapp.end()){
                mapp[A[i]] = i;
                while (mapp[A[suf]] != suf){
                    suf++;
                }
            }
            else {
                mapp.erase(A[suf++]);
                mapp[A[i]] = i;
                pre = suf;
                while (mapp[A[suf]] != suf){
                    suf++;
                }
            }
            ans += suf - pre + 1;
        }
        return ans;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值