Given an array of integers nums
and an integer k
. A continuous subarray is called nice if there are k
odd numbers on it.
Return the number of nice sub-arrays.
Example 1:
Input: nums = [1,1,2,1,1], k = 3
Output: 2
Explanation: The only sub-arrays with 3 odd numbers are [1,1,2,1] and [1,2,1,1].
Example 2:
Input: nums = [2,4,6], k = 1
Output: 0
Explanation: There is no odd numbers in the array.
Example 3:
Input: nums = [2,2,2,1,2,2,1,2,2,2], k = 2
Output: 16
Constraints:
1 <= nums.length <= 50000
1 <= nums[i] <= 10^5
1 <= k <= nums.length
這道題給了一個數組 nums,和一個正整數k,定義了一種 nice 子數組,即子數組中有k個奇數,現在問有多少個 nice 子數組。由於求的是子數組,所以必須是連續的,而如果把每個子數組當作一個窗口的話,就不難想到可以用滑動窗口 Sliding Window 來做,LeetCode 中使用滑動窗口的題目還挺多的,可以參見末尾列出的類似題目。在不遍歷所有子數組的情況下,求正好有K個奇數並不是很容易,這道題需要稍稍轉換一下思維,比較好求的求最多有K個奇數的情況。若能分別求出最多有K個奇數的子數組的個數,和最多有 K-1 個奇數的子數組的個數,二者相減,就是正好有K個奇數的子數組的個數。
我們之前做過這樣一道題目 Subarrays with K Different Integers,這里采用幾乎完全一樣的思路。由於要同時求K和 K-1 的情況,所以可以用個子函數來做。在 atMost 函數中,定義窗口左邊界的位置 left,初始化為0。然后開始遍歷,對於每個遍歷到的數字,若是奇數,則k自減1。由於窗口內不能有超過k個的奇數,所以當k小於0時,要進行循環,縮小窗口,即右移左邊界,當移除一個奇數的時候,k就自增1。此時窗口的大小就代表了此時最多有k個奇數的子數組的個數,將其加入結果 res,這樣直至 for 循環退出后,就可以得到最終的結果了,參見代碼如下:
解法一:
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
return atMost(nums, k) - atMost(nums, k - 1);
}
int atMost(vector<int>& nums, int k) {
int res = 0, left = 0, n = nums.size();
for (int i = 0; i < n; ++i) {
k -= nums[i] % 2;
while (k < 0) {
k += nums[left++] % 2;
}
res += i - left + 1;
}
return res;
}
};
再來看一種解法,這種思路的核心是統計偶數的個數,只需要一次遍歷即可完成。還是要借用滑動窗口的方法,維護一個剛好有k個奇數的窗口,當首次將k個奇數放到窗口中時,當前窗口可能不是最短的,需要統計左起連續0的個數。比如數組數組是 [0, 0, 1, 0, 1, 0, 0],k=2 時,則首次找到的窗口是 [0, 0, 1, 0, 1],顯然不是最短的,這里最短的窗口為 [1, 0, 1],需要統計左起0的個數,有兩個0,再加上最短的那個窗口,目前為止總共有三個子數組符合題意,加到結果 res 中。然后繼續右移右邊界,若還是遇到了偶數,則之前所有的情況再加上這個偶數,就又是一個新的子數組,所以可以直接把 cnt 再加到結果 res 中,直到遇到奇數,cnt 清零,開始一輪同樣的處理,參見代碼如下:
解法二:
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
int res = 0, left = 0, cnt = 0, n = nums.size();
for (int i = 0; i < n; ++i) {
if (nums[i] % 2 == 1) {
--k;
cnt = 0;
}
while (k == 0) {
k += nums[left++] & 1;
++cnt;
}
res += cnt;
}
return res;
}
};
這道題如果把奇數都當作1,偶數都當作0,那么含有k個奇數的子數組其實就是和為k的子數組,就變成之前那道 Subarray Sum Equals K 了,用一個 HashMap 來建立子數組之和跟其出現次數之間的映射,初始化要加入 {0,1} 這對映射,這是為啥呢,因為解題思路是遍歷數組中的數字,用 cur 來記錄到當前位置的累加和,建立 HashMap 的目的是為了可以快速的查找 cur-k 是否存在,即是否有連續子數組的和為 cur-k,如果存在的話,那么和為k的子數組一定也存在,這樣當 cur 剛好為k的時候,那么數組從起始到當前位置的這段子數組的和就是k,滿足題意,如果 HashMap 中事先沒有 m[0] 項的話,這個符合題意的結果就無法累加到結果 res 中,這就是初始化的用途。上面講解的內容順帶着也把 for 循環中的內容解釋了,這里就不多闡述了,有疑問的童鞋請在評論區留言哈,參見代碼如下:
解法三:
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
int res = 0, cur = 0, n = nums.size();
unordered_map<int, int> m{{0, 1}};
for (int i = 0; i < n; ++i) {
cur += nums[i] & 1;
res += m[cur - k];
++m[cur];
}
return res;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/1248
類似題目:
Number of Substrings Containing All Three Characters
Replace the Substring for Balanced String
Subarrays with K Different Integers
Shortest Subarray with Sum at Least K
參考資料:
https://leetcode.com/problems/count-number-of-nice-subarrays/
https://leetcode.com/problems/count-number-of-nice-subarrays/discuss/419483/Subarray-Sum-Equals-K