題目描述
給你一個整數數組
nums
和一個整數
k
。如果某個 連續 子數組中恰好有
k
個奇數數字,我們就認為這個子數組是「優美子數組」。請傳回這個數組中「優美子數組」的數目。
輸入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
輸出:16
解題思路
自己的第一反應是用“滑動視窗”的解法,但是沒想清楚怎麼弄,還是欠火候啊。。。
- 滑動視窗( O ( n ) + O ( 1 ) O(n) + O(1) O(n)+O(1),推薦!):
- 不斷右移
指針來擴大滑動視窗,使其包含right
個奇數;k
- 若目前滑動視窗包含了
個奇數,則如下「計算目前視窗的優美子數組個數」:k
- 統計第
個奇數左邊的偶數個數1
。 這leftEvenCnt
個偶數都可以作為「優美子數組」的起點,是以起點的選擇有leftEvenCnt
種(因為可以一個偶數都不取,是以别忘了 +1 喔)。leftEvenCnt + 1
- 統計第
個奇數右邊的偶數個數k
。 這rightEvenCnt
個偶數都可以作為「優美子數組」的終點,是以終點的選擇有rightEvenCnt
種(因為可以一個偶數都不取,是以别忘了 +1 喔)。rightEvenCnt + 1
- 是以「優美子數組」左右起點的選擇組合數為
。(leftEvenCnt + 1) * (rightEvenCnt + 1)
- 統計第
- 不斷右移
- 字首和( O ( n ) + O ( n ) O(n) + O(n) O(n)+O(n),有點不好了解,看看就好):
- 計算字首和數組
:周遊原數組,每周遊一個元素,計算目前的字首和(即到目前元素為止,數組中有多少個奇數);arr
- 對上述字首和數組,雙重循環統計
的個數,這樣做是 O(N^2)O(N2) 的(這裡會逾時哦)。arr[j] - arr[i] == k
- 優化:是以,我們可以像「1. 兩數之和」那樣使用
優化到 O(N)O(N),鍵是「字首和」,值是「字首和的個數」(下面代碼中具體使用的是HashMap
數組,下标是「字首和」,值是「字首和的個數」),是以我們可以周遊原數組,每周遊到一個元素,計算目前的字首和int[] prefixCnt
,就在sum
中累加上字首和為res
的個數。sum - k
- 計算字首和數組
參考代碼
滑動視窗
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
int left = 0, right = 0, oddCnt = 0, res = 0;
while (right < nums.length) {
// 右指針先走,每遇到一個奇數則 oddCnt++。
if ((nums[right++] & 1) == 1) {
oddCnt++;
}
// 若目前滑動視窗 [left, right) 中有 k 個奇數了,進入此分支統計目前滑動視窗中的優美子數組個數。
if (oddCnt == k) {
// 先将滑動視窗的右邊界向右拓展,直到遇到下一個奇數(或出界)
// rightEvenCnt 即為第 k 個奇數右邊的偶數的個數
int tmp = right;
while (right < nums.length && (nums[right] & 1) == 0) {
right++;
}
int rightEvenCnt = right - tmp;
// leftEvenCnt 即為第 1 個奇數左邊的偶數的個數
int leftEvenCnt = 0;
while ((nums[left] & 1) == 0) {
leftEvenCnt++;
left++;
}
// 第 1 個奇數左邊的 leftEvenCnt 個偶數都可以作為優美子數組的起點
// (因為第1個奇數左邊可以1個偶數都不取,是以起點的選擇有 leftEvenCnt + 1 種)
// 第 k 個奇數右邊的 rightEvenCnt 個偶數都可以作為優美子數組的終點
// (因為第k個奇數右邊可以1個偶數都不取,是以終點的選擇有 rightEvenCnt + 1 種)
// 是以該滑動視窗中,優美子數組左右起點的選擇組合數為 (leftEvenCnt + 1) * (rightEvenCnt + 1)
res += (leftEvenCnt + 1) * (rightEvenCnt + 1);
// 此時 left 指向的是第 1 個奇數,因為該區間已經統計完了,是以 left 右移一位,oddCnt--
left++;
oddCnt--;
}
}
return res;
}
}
字首和
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
// 數組 prefixCnt 的下标是字首和(即目前奇數的個數),值是字首和的個數。
int[] prefixCnt = new int[nums.length + 1];
prefixCnt[0] = 1;
// 周遊原數組,計算目前的字首和,統計到 prefixCnt 數組中,
// 并且在 res 中累加上與目前字首和內插補點為 k 的字首和的個數。
int res = 0, sum = 0;
for (int num: nums) {
sum += num & 1;
prefixCnt[sum]++;
if (sum >= k) {
res += prefixCnt[sum - k];
}
}
return res;
}
}