[LeetCode] 1248. Count Number of Nice Subarrays 統計優美子數組



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

Max Consecutive Ones III

Binary Subarrays With Sum

Subarrays with K Different Integers

Fruit Into Baskets

Shortest Subarray with Sum at Least K

Minimum Size Subarray Sum

Subarray Sum Equals 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

https://leetcode.com/problems/count-number-of-nice-subarrays/discuss/419378/JavaC%2B%2BPython-Sliding-Window-O(1)-Space


LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM