LeetCode#1428 統計“優美子數組” Java

@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 代碼實現,整個算法的時間複雜度和空間複雜度都是 O(n)O(n) 的。


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% 的用戶

數學方法

這道題官方給出了兩種解法,其中,第一種數學方法的思想與前面我的動態規劃法類似。雖然時間複雜度也是 O(n)O(n),不過,官方解法的常數要小一點,所以實際運行時間要比我的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賜教。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章