题目链接:https://leetcode.cn/problems/count-subarrays-where-max-element-appears-at-least-k-times/description/
我的思路
想到用滑动窗口双指针,但是在结果计算和指针移动的判断上还是有些不太对,思路是清晰的,离正确答案之差一点点。首先求出$nums$中的最大值记为$m$,初始化左指针$l=0$,定义一个变量$cnt$统计枚举过程中最大值出现了几次,接着用enumerator枚举$r,x$,如果$x=m$,则将$cnt+1$,当$cnt\geq k$时,进入while循环判断,此时意味着到了某个符合条件子数组的边界,$ans+1$,如果$nums[l]=m$,则将$cnt-1$,同时将左指针右移$l+1$,因为我们要统计的是子数组的个数,所以在考察完每个$r,x$时,再将左指针重置,$l=0$,以免漏掉情况,如此循环判断下去,返回$ans$
分析
这个思路看上去很正确,但存在考察不全的情况,尽管想到了重置左指针,但由于$cnt$的值动态变化,所以会漏掉计数。比如案例$[1,3,2,3,3], k = 2$,按照上面的思路,将每一步计算的$cnt$值和子数组打印出来得到:
2 [1, 3, 2, 3]
2 [3, 2, 3]
2 [1, 3, 2, 3, 3]
2 [3, 2, 3, 3]
到这里程序就退出运行了,原因出在考察到第三个3时。在$r=3$时,对应的$nums[3]=3$为第二个3,此时$cnt=2$,满足while循环进入条件,接着开始计数,做指针开始右移,得到一开始的两个结果(前两行),但由于$l$移动到1时,$nums[1]$也是3,因此$cnt-1$,$cnt$变为1,跳出循环,$l$重置为0,继续考察数组元素,$r=4,x=3$,$cnt$再加1,得到$cnt=2$,满足while进入条件,开始计数,得到后面两行的结果,但同样的,当$l$再次移动到1时,$nums[1]=3$,使得$cnt-1$,变成1,不再符合while的条件,便跳出循环了,得到错误的结果。
纠正
滑动窗口问题中,应该不走回头路,试想当$r=3$时,左指针开始移动,滑动到$l=1$时碰到最大值,那么说明$nums[1]$的这个3不能再用了(不要回头,不然会很复杂),我们要将这种情况下的所有可能全部计算出来,接着考虑后面的元素,每一个元素都如此,便能计算到最准确的结果
回到上面那个情况,$r=3,x=3$,进入while判断,$l$到1后$cnt-1$,$l$再加1后跳出循环(因为$l+1$是写在while中的最后一句),此时$l$变为2,那么此时对应的,在右边界为$r=3$的情况下,符合条件的数组数不就是2个吗?因此只需要$ans+l$即可,非常巧妙
换句话说,其实我们只要找到每个“临界”情况,再将此时的$l$累加起来,就是最后的答案。再看$r=4$的情况,上一个循环中$l$被赋值为2,那么同样地,$l$会以4跳出while循环,此时对应的符合条件的数组不就是4个嘛,分别是$[1,3,2,3,3],[3,2,3,3],[2,3,3],[3,3]$,因为有最后一个元素的加进来,所以不会考察到与之前重复的情况,最后将4和2相加,6就是最终的答案
反思
- 双指针/滑动窗口问题中,一般不会将左指针进行重置,一方面容易考察重复情况产生错误,另一方面思路会不够清晰,容易搞混
- 这题还有一个优化的点是,可以直接利用$k$,节省内存占用,遇到最大值就将其减1,while的循环进入条件为$k=0$,当左指针滑到最大值时,再将$k+1$即可,这样可以节约一些内存
- 其实做题也是一个编码-解码的过程,我们的大脑中存在着latent空间,需要将题目进行解码,知道它是什么意思,同时也要将答案进行编码,二者要在latent上匹配,本质上讲的都是一个东西,二者在高维空间中应该有相似的表示,这样才是最佳的解答
结果
时间复杂度:$O(N)$
空间复杂度:$O(1)$
已是最优解