Given an array A
of positive integers, call a (contiguous, not necessarily distinct) subarray of A
good if the number of different integers in that subarray is exactly K
.
(For example, [1,2,3,1,2]
has 3
different integers: 1
, 2
, and 3
.)
Return the number of good subarrays of A
.
Example 1:
Input: A = [1,2,1,2,3], K = 2
Output: 7
Explanation: Subarrays formed with exactly 2 different integers: [1,2], [2,1], [1,2], [2,3], [1,2,1], [2,1,2], [1,2,1,2].
Example 2:
Input: A = [1,2,1,3,4], K = 3
Output: 3
Explanation: Subarrays formed with exactly 3 different integers: [1,2,1,3], [2,1,3], [1,3,4].
Note:
1 <= A.length <= 20000
1 <= A[i] <= A.length
1 <= K <= A.length
這道題給了一個只有正整數的數組A,然后定義了一種好子數組,需要該子數組中不同的數字的個數正好為給定的正數K。這種玩子數組個數的題目,如果是求極值,大概率是要用動態規划做,不過這里是求滿足某個條件的子數組的個數,還有一種比較常見的解法,就是滑動窗口 Sliding Window,這是一種非常重要的解題思路,也經常在面試中出現,所以務必需要掌握。LeetCode 中關於滑動窗口的題還蠻多的,可以參見下方的類似題目區域,列出了一堆堆。在不遍歷所有子數組的情況下,求正好有K個不同的數字並不是很容易,這道題需要稍稍轉換一下思維,比較好求的求最多有K個不同的數字的情況。若能分別求出最多有K個不同數字的子數組的個數,和最多有 K-1 個不同數字的子數組的個數,二者相減,就是正好有K個不同數字的子數組的個數。我們之前做過這樣一道題目 Longest Substring with At Most K Distinct Characters,求最多有K個不同字符的最長子串,這里就是用的相同的滑動窗口的思路來做。
由於要同時求K和 K-1 的情況,所以可以用個子函數來做。在 helper 函數中,使用一個 HashMap 來建立每個數字和其出現次數之間的映射,再用個變量 left 標記窗口的左邊界。下面進行 for 循環,若當前的數字沒出現過,則K自減1,因為此時子數組中多了一個不同的數字,然后該數字的映射值自增1。此時K值有可能小於0了,說明子數組中的不同數字過多了,需要移除一個,用個 while 循環,若K小於0則進行循環,此時左邊界上的數字的映射值自減1,減小后若為0了,則說明該數字已經徹底被移出了滑動窗口,此時K應該自增1,同時左邊界 left 自增1,表示向右移動一位。此時這個窗口的長度就代表了此時最多有k個不同數字的子數組的個數,將其加入結果 res,這樣直至 for 循環退出后,就可以得到最終的結果了,參見代碼如下:
解法一:
class Solution {
public:
int subarraysWithKDistinct(vector<int>& A, int K) {
return helper(A, K) - helper(A, K - 1);
}
int helper(vector<int>& A, int K) {
int n = A.size(), res = 0, left = 0;
unordered_map<int, int> numCnt;
for (int i = 0; i < n; ++i) {
if (numCnt[A[i]] == 0) --K;
++numCnt[A[i]];
while (K < 0) {
if (--numCnt[A[left]] == 0) ++K;
++left;
}
res += i - left + 1;
}
return res;
}
};
滑動窗口有多種寫法,比如下面這種並不直接減小K,而是用 HashMap 中的映射個數來跟K比較,不過這樣的話就一定要移除不在窗口內的映射才行,參見代碼如下:
解法二:
class Solution {
public:
int subarraysWithKDistinct(vector<int>& A, int K) {
return helper(A, K) - helper(A, K - 1);
}
int helper(vector<int>& A, int K) {
int n = A.size(), res = 0, left = 0;
unordered_map<int, int> numCnt;
for (int i = 0; i < n; ++i) {
++numCnt[A[i]];
while (numCnt.size() > K) {
if (--numCnt[A[left]] == 0) numCnt.erase(A[left]);
++left;
}
res += i - left + 1;
}
return res;
}
};
再來看另一種寫法,這里建立的是數字和其下標之間的映射,而不是其出現的個數,不過整體的思路並沒有啥太大的區別,參見代碼如下:
解法三:
class Solution {
public:
int subarraysWithKDistinct(vector<int>& A, int K) {
return helper(A, K) - helper(A, K - 1);
}
int helper(vector<int>& A, int K) {
int n = A.size(), res = 0, left = 0;
unordered_map<int, int> num2idx;
for (int i = 0; i < n; ++i) {
num2idx[A[i]] = i;
while (num2idx.size() > K) {
if (num2idx[A[left]] == left) num2idx.erase(A[left]);
++left;
}
res += i - left + 1;
}
return res;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/992
類似題目:
Number of Substrings Containing All Three Characters
Count Number of Nice Subarrays
Replace the Substring for Balanced String
Max Consecutive Ones III
Shortest Subarray with Sum at Least K
Longest Substring with At Most Two Distinct Characters
Longest Substring with At Least K Repeating Characters
Longest Substring with At Most K Distinct Characters
參考資料:
https://leetcode.com/problems/subarrays-with-k-different-integers/