0x01.問題
給你一個整數數組 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
public int numberOfSubarrays(int[] nums, int k)
0x02.詳細思路分析
第一步,讀題,發現題目的需求:
- 在數組中找出一些連續子數組,統計這些連續子數組的數目。
- 這些連續子數組需要滿足的條件是,奇數個數等於
k
。
理清一下思路,發現着重點在於尋找子數組的數目,於是,一條很明顯的思路就出來了,那就是,遍歷所有可能的連續子數組,簡單的寫出下列的代碼:
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
int ans=0;
for(int i=0;i<nums.length;i++){
int cou=0;
for(int j=i;j<nums.length;j++){
if((nums[j]&1)==1){
cou++;
}
if(cou==k){
ans++;
}
if(cou>k){
break;
}
}
}
return ans;
}
}
應該可以說已經解決了這個問題,但我們看一下時間複雜度:O(N^2)
,這很明顯太高了,當數據量達到10^5
的時候,已經無法短時間運算出來了。
我們需要對這個算法進行優化。
其實我們在寫這個算法的時候應該已經感覺到這是一種不好的思路,爲什麼呢?因爲我們在寫這個代碼的時候,就會發現,過於暴力,感覺到似乎做了很多不必要的重複運算。
其實我們的這種感覺,在指引着我們來對算法進行優化,我們的感覺是,重複運算的次數過多。在每一次更換子數組的起始點的時候,我們都要對後面的每個數進行判斷,但其實,這裏面的數很多已經在前面都判斷過了,我們想,如果只判斷一次就好了。
我們判斷這些數的目的是什麼?目的就是統計這個區間內奇數的數量,然後判斷這個子數組是否滿足條件。
我們要想突破O(N^2)
的這個瓶頸,就一定不能去遍歷子數組,因爲遍歷子數組的話,O(N^2)
是絕對少不了的。那麼我們就只剩下另一條思路,由奇數的個數,去計算出滿足條件的子數組的數目。
假如,我們知道了在一段區間內,恰好有k
個奇數,我們怎麼去計算有多少滿足條件的連續子數組數目呢?
- 首先,對於這段區間,最後一個數肯定是奇數,因爲是通過最後一個數判斷出來奇數個數等於
k
的,這段區間的第一個數暫時不清楚。 - 然後,對於求連續子數組的個數,最重要的是,確定子數組的起始點和終點,只要在起始點和終點之內包含了
k
個奇數,那麼都是滿足條件的。 - 假如,我們從第一個數開始向右邊尋找這段區間內的第一個奇數,並記錄下中間偶數的數目,這些偶數都是可以作爲連續子數組的起始點的,因爲不管怎樣,這段區間的第一個奇數都還在它們後面。
- 再從最後一個數開始,向右去尋找下一個奇數,並記錄中間偶數的數目,這些偶數都是可以當作連續子數組的尾部的,因爲這段區間內最後一個奇數已經出現。
- 最後這段區間內的連續子數組的個數就是:
起始點數目
*終點數目
,也就是這些的組合數。
通過上述思路,我們可以基本確定,如何遍歷一次去得出滿足條件的子數組的個數,接下來,確定整體的思路:
- 採用滑動窗口的思路,每次構造出一段滿足區間內奇數個數恰爲
k
的區間,然後計算這段區間。 - 計算完畢後,左指針右移,相應的奇數奇數器減一,不斷重複上述過程。
0x03.解決代碼–滑動窗口
class Solution {
public int numberOfSubarrays(int[] nums, int k) {
int ans=0;
int oddCou=0;
int left=0;
int right=0;
while(right<nums.length){
if((nums[right++]&1)==1){
oddCou++;
}
if(oddCou==k){
int preRight=right;
while(right<nums.length&&(nums[right]&1)==0){
right++;
}
int rightEvenCou=right-preRight;
int leftEvenCou=0;
while((nums[left]&1)==0){
leftEvenCou++;
left++;
}
ans+=(leftEvenCou+1)*(rightEvenCou+1);
left++;
oddCou--;
}
}
return ans;
}
}
ATFWUS --Writing By 2020–04-21