本文在別人的題解【鏈接列於參考資料中】基礎上加上些許自己的理解,若侵權立刪。
問題描述
給你一個整數數組 nums 和一個整數 k。
如果某個 連續 子數組中恰好有 k 個奇數數字,我們就認爲這個子數組是「優美子數組」。
請返回這個數組中「優美子數組」的數目。
示例 1:
輸入:nums = [1,1,2,1,1], k = 3
輸出:2
解釋:包含 3 個奇數的子數組是 [1,1,2,1] 和 [1,2,1,1] 。
解題報告
記錄奇數位置
我們發現,如果兩個奇數之間(包含自身)一共包含了 k
個奇數,那麼這 k
個奇數可以構成的連續子數組個數就是 左邊連續的偶數 的個數加 1
乘以 右邊連續的偶數 的個數加 1
。
所以 問題的關鍵就變成如何高效的求每個奇數前 連續的偶數 的個數?
一種解決方案是記錄每個奇數所在的位置,那麼每個奇數前 連續的偶數 個數爲當前奇數的位置減去上一個奇數的位置。
另外在實現的時候需要注意一些細節問題,例如第一個奇數前 連續偶數的個數 如何求?最後一個奇數前 連續偶數的個數 如何求?
解決方案是:在最開始添加上虛擬位置 -1
,在最後添加虛擬位置 n
。
最後遍歷所有的 i
, 將第 i
個 1
作爲起點,然後累加答案即可。
時間複雜度:
空間複雜度:
滑動窗口
仔細想一想,上面的思想完全可以用滑動窗口來實現,省去記錄每個 1
的空間。
- 移動窗口的右邊界時,更新窗口內
1
出現的次數; - 當窗口內
1
的數目達到要求的k
時,開始縮收左邊界; - 移動左邊界時,記錄縮收過程遇到的
0
的個數 even,直到遇到1
; - 累加當前的
even
。
時間複雜度:
空間複雜度:
前綴和
這種 做法 和 哈希表優化系列【空間換時間】-Leetcode 560.和爲 K 的子數組 有些許相似之處,都用了前綴和。不同的其中一點是:在 Leetcode 560
中,由於前綴和存在負數的可能,所以需要用哈希表來存儲前綴和,便於查找,但是此題中,前綴和的範圍是可控的而且爲正整數,所以我們完全可以將前綴和作爲 vector
的索引來存儲。
同樣都是前綴和,但是具體實現上,我這種菜雞,還是挺困難的。具體想法和滑動窗口有些相似。
實現代碼
記錄奇數位置實現
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
int n = nums.size();
vector<int> pos;
pos.push_back(-1);
for (int i = 0; i < n; ++i) {
if (nums[i]&1) pos.push_back(i);
}
pos.push_back(n);
int res = 0, sz = pos.size();
for (int i = 1; i+k < sz; ++i) {
res += (pos[i] - pos[i-1]) * (pos[i+k] - pos[i+k-1]);
}
return res;
}
};
滑動窗口實現
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
int n = nums.size();
int res = 0, cnt = 0, even = 0;
int l = 0, r = 0;
while (r < n) {
if (cnt < k && (nums[r++]&1)) cnt++;
if (cnt == k) {
even = 1;
while (!(nums[l++]&1)) even++;
cnt--;
}
res += even;
}
return res;
}
};
前綴和+哈希表優化實現
class Solution {
public:
int numberOfSubarrays(vector<int>& nums, int k) {
int n = nums.size();
vector<int> count(n+1, 0);
count[0] = 1;
int res = 0, odd = 0;
for (int i = 0; i < n; ++i) {
odd += nums[i]&1;
if (odd >= k) res += count[odd-k];
count[odd]++;
}
return res;
}
};
參考資料
[1] Leetcode 1248. 統計「優美子數組」
[2] 【每日算法Day 107】面試必考:良心推薦,一題三解,不看後悔一輩子