@author: sdubrz
@date: 2020.04.21
題號: 1428
題目難度: 中等
原題鏈接 https://leetcode-cn.com/problems/count-number-of-nice-subarrays/submissions/
題目的著作權歸領釦網絡所有,商業轉載請聯繫官方授權,非商業轉載請註明出處。
解題代碼轉載請聯繫 lwyz521604#163.com
給你一個整數數組 nums 和一個整數 k。
如果某個 連續 子數組中恰好有 k 個奇數數字,我們就認爲這個子數組是**「優美子數組」**。
請返回這個數組中「優美子數組」的數目。
示例 1:
輸入:nums = [1,1,2,1,1], k = 3
輸出:2
解釋:包含 3 個奇數的子數組是 [1,1,2,1] 和 [1,2,1,1] 。
示例 2:
輸入:nums = [2,4,6], k = 1
輸出:0
解釋:數列中不包含任何奇數,所以不存在優美子數組。
示例 3:
輸入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
輸出:16
提示:
- 1 <= nums.length <= 50000
- 1 <= nums[i] <= 10^5
- 1 <= k <= nums.length
通過次數13,516提交次數25,446
動態規劃解法
這道題是今天的每日一題,由於不是按照標籤來選的題,所以在讀題之前也不知道這道題考察的是那個數據結構或算法。讀完題目之後感覺這道題可以用動態規劃來解決。
首先,對於輸入的數組 nums ,這道題關心的只是 nums 中的每個元素是偶數還是奇數,至於具體的值是多少是不需要關心的。因而可以直接把 nums 中的數組替換爲對 2 取餘。替換之後,值爲1表示這裏是個奇數,值爲0表示這裏是個偶數。
接着,我們可以統計出 nums 中每個奇數出現的位置,對於 Java 來講,可以用一個 ArrayList 來存儲這個信息。在下面的程序中 label[i] 表示的是第 i 個奇數出現的位置。如果 nums 中的奇數個數小於 k 可以直接返回0。
用動態規劃的思想,對於前 x 個元素組成的子數組,我們可以根據前 x-1 個元素組成的子數組中的“優美子數組”個數來推測出前 x 個元素組成的子數組中“優美子數組”的個數。根據 nums[x] 是否爲奇數,可以分爲兩種情況:
- 如果 nums[x] 不是奇數。會增加一定數量的包含 nums[1…x-1] 中最右端的 k 個奇數的“優美子數組”。
- 如果 nums[x] 是奇數。會增加一定數量的包含 nums[x] 和 nums[1…x-1] 中最右端的 k-1 個奇數的“優美子數組”。
一個比較重要的事情就是確定上面提到的“一定數量”究竟是多少。以第一種情況爲例,對於子數組 nums[1…x-1]來說,這個一定數量取決於最右端的 k 個奇數週圍的情況。如下圖所示,如果 nums[x] 爲偶數,會增加一些包含目前子數組中最右端的 k 個奇數的“優美子數組”,其具體個數取決於從右邊數第 k 個奇數和第 k+1 個奇數之間的偶數個數,具體爲這個偶數個數加一。對於第二種情況也有類似的計算方法。
這樣,我們就可以迭代地計算整個數組中“優美子數組”的個數了,下面是具體的 Java 代碼實現,整個算法的時間複雜度和空間複雜度都是 的。
import java.util.*;
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
int n = nums.length;
if(n<k) {
return 0;
}
int[] count = new int[n];
//int[] sum = new int[n];
ArrayList<Integer> label = new ArrayList<>();
label.add(0);
for(int i=0; i<n; i++) {
nums[i] = nums[i]%2;
if(nums[i]==1) {
label.add(i);
}
}
if(label.size()<=k) {
return 0;
}
for(int i=0; i<label.get(k); i++) {
count[i] = 0;
}
count[label.get(k)] = 1 + label.get(1);
int left = 0;
int right = label.get(1);
int step = 1;
for(int i=label.get(k)+1; i<n; i++) {
if(nums[i]==0) {
count[i] = count[i-1] + right - left + 1;
}else {
step++;
left = right + 1;
right = label.get(step);
count[i] = count[i-1] + right-left+1;
}
}
return count[n-1];
}
}
在 LeetCode 系統中提交的結果爲
執行結果: 通過 顯示詳情
執行用時 : 17 ms, 在所有 Java 提交中擊敗了 40.80% 的用戶
內存消耗 : 48.6 MB, 在所有 Java 提交中擊敗了 100.00% 的用戶
數學方法
這道題官方給出了兩種解法,其中,第一種數學方法的思想與前面我的動態規劃法類似。雖然時間複雜度也是 ,不過,官方解法的常數要小一點,所以實際運行時間要比我的DP版本稍快。下面是官方解法的解釋和代碼截圖。
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
int n = (int)nums.size();
int odd[n + 2], ans = 0, cnt = 0;
for (int i = 0; i < n; ++i) {
if (nums[i] & 1) odd[++cnt] = i;
}
odd[0] = -1, odd[++cnt] = n;
for (int i = 1; i + k <= cnt; ++i) {
ans += (odd[i] - odd[i - 1]) * (odd[i + k] - odd[i + k - 1]);
}
return ans;
}
};
前綴和 + 差分
這是官方給出的第二種解法
class Solution {
vector<int> cnt;
public:
int numberOfSubarrays(vector<int>& nums, int k) {
int n = (int)nums.size();
cnt.resize(n + 1, 0);
int odd = 0, ans = 0;
cnt[0] = 1;
for (int i = 0; i < n; ++i) {
odd += nums[i] & 1;
ans += odd >= k ? cnt[odd - k] : 0;
cnt[odd] += 1;
}
return ans;
}
};
本文是我在 LeetCode 刷題的筆記,如有不當之處歡迎各位大神通過留言或QQ賜教。