K 个不同整数的子数组(滑动窗口)
先给题
给定一个正整数数组 A,如果 A 的某个子数组中不同整数的个数恰好为 K,则称 A 的这个连续、不一定独立的子数组为好子数组。
(例如,[1,2,3,1,2] 中有 3 个不同的整数:1,2,以及 3。)
返回 A 中好子数组的数目。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/subarrays-with-k-different-integers
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
1.暴力破解
这个也就不详谈了,一个一个去比较,结果肯定超时。
2.自己想的
当我看到子数组时,我想到了滑动窗口的解法,之前我也做过这样的题了,在https://www.cnblogs.com/lisuhang/p/14386502.html,没看的可以看一下。
我个人的解法其实是对暴力的一种优化,(做的题太少,没有想到关键点)
假设此时我们的子数组是[i,j],那么下一个j+1,如何判断[i,j + 1]符不符合题意呢?让第j+1个元素与前面出现过的元素比较,如果没出现过,那么sum++,出现过则sum的值不变。
接下来只要判断sum的值符不符合K了,如果符合,那么我们可以继续向右边滑动,如果不符合,那么我们就要左边滑动缩小范围。
原理上是这样的,但当我们左指针滑动时,去除的那个数对sum的值有没有影响呢?我们还需要再判断。如果依次比较,太耗时间了。我在这里想了一个影响值的设定。
影响值是对sum的影响值。打个比方,一开始有一个1,那么这个1 对sum的影响值 就是1,它的加入上sum++了。接下来变成1 2,2与1不同,所以2的影响值也是1,1的影响值不变,接下来变成 1 2 1,那么此时第一个1的影响值就变为0,因为它的加入是不影响sum的值的。
然后我们在保证每时每刻子数组第一位数的影响值始终是1,那么只要做窗口移动,sum的值一定会受到影响。
如果仅这样做,我们会丢掉一些符合条件的子数组,就拿上面的例子 1 2 1 如果K=2,那么符合的子数组有三个 1 2,2 1,1 2 1.而按照我的影响值,结果就成了1 2和2 1,忽略掉了 1 2 1。那么我们怎么来解决这个问题呢?
我加了一个zero的值来记录我们到底忽略了前面影响值为0的元素数目。只要是因为首元素影响值为0而左窗口移动的,我们就让zero++(影响值为0,加不加都不会影响sum值,但加上它也是一个符合的子数组),只有因为sum>k 而左窗口移动时,zero重置为0(因为此时我们跳过了一个影响值为1的元素,此时是超过K了,所以前面的zero所记录的值就没有用了)。
接下来就是影响值如何设置了,我们需要右窗口指针指向的元素数值从j-1开始到i依次比较,只要遇到相同的那么就把匹配到的这个元素的影响值设为0,停止循环,并给右端元素的影响值赋值为1,此时sum值不变;如果没遇到,那么sum++。
我们知道,当左窗口移动而右窗口没有移动的时候,此时我们无需再判断影响值。那么只要是右窗口移动,而且j的值在规定范围中,我们便让它去判断影响值,否则不去判断。
1 int subarraysWithKDistinct(vector<int>& A, int K) { 2 if (K == 0) return 0; 3 int n = 0;//记录好子数组的数量 4 int sum = 0;//记录当前子数组的不同元素个数之和 5 int zero = 0; 6 int* sum_add = new int[A.size()]; 7 int i = 0, j = 0; 8 int flag = 1; 9 sum_add[i] = 1; 10 while (i <= j) { 11 if (flag) { 12 for (int s = j - 1; s >= i; s--) { 13 if (A[j] == A[s]) { 14 sum_add[s] = 0; 15 sum--;//后面sum++,因为有相同的,sum就不再改变,所以这里让它--; 16 break; 17 } 18 } 19 sum++; 20 sum_add[j] = 1; 21 } 22 while (sum_add[i] == 0) { 23 i++; 24 zero++; 25 } 26 if (sum == K) 27 n += zero + 1; 28 if (sum <= K && j < A.size() - 1) { 29 j++; 30 flag = 1; 31 } 32 else { 33 zero = 0; 34 sum--; 35 i++; 36 flag = 0; 37 } 38 } 39 return n; 40 }
这是我写的代码,因为越界问题,做了很久。(晚上静了静才终于搞定了)。
通过是通过了,而且内存方面也没太大问题,但是速度方面却只是勉强通过。
3.理解题解
刚看题解有点蒙,自己基础不牢靠过于着急了(每日一题是hard就很难受)。
对于双指针的理解和适用范围我还不够了解,本题当中是我们用双指针的想法解决的不是恰好的问题,而是最多的问题。

来源https://leetcode-cn.com/problems/subarrays-with-k-different-integers/solution/k-ge-bu-tong-zheng-shu-de-zi-shu-zu-by-l-ud34/
那么如何求指定范围中有多少个最大不同个数为K的子数组呢?
我们用动态规划想一下,当[a,b,c]符合题意,[a,b,c,d]加入d后,多出了哪些子数组?d,c d,b c d,a b c d。right - low +1。(https://leetcode-cn.com/problems/subarrays-with-k-different-integers/solution/c-hua-dong-chuang-kou-li-jie-right-left-vzp76/ ,从这里受教的)
那么这样就简单了,每一次移动好后,让记录的值加上右边界减去左边界+1,就是移动后新增加的符合题意的子数组了。
1 class Solution { 2 public: 3 int atMostKDistinct(vector<int>& A, int K) { 4 vector<int> a(A.size() + 1, 0);//a用来记录A中出现的每个元素的次数。 5 cout << endl; 6 int i = 0, j = 0; 7 int sum = 0; 8 int r = 0; 9 while (j < A.size()) { 10 if (a[A[j]] == 0) 11 sum++; 12 a[A[j]]++; 13 j++; 14 while (sum > K) { 15 a[A[i]]--; 16 if (a[A[i]] == 0) 17 sum--; 18 i++; 19 } 20 r += j - i; 21 } 22 cout << r << endl; 23 return r; 24 } 25 int subarraysWithKDistinct(vector<int>& A, int K) { 26 return atMostKDistinct(A, K) - atMostKDistinct(A, K - 1); 27 } 28 };
这是修改后的代码

浙公网安备 33010602011771号