數組-大小爲 K 且平均值大於等於閾值的子數組數目(力扣 1343)

題目鏈接 點我

思路

基本概念要掌握

子數組:一個或多個連續原數組中元素組成子數組
子序列:在原序列中抽取一個或多個元素組成子序列
即子數組必須連續,子序列不一定連續
作者本人在第一次做題時沒有思路就是沒有搞清楚這兩個概念。

思路一

在瞭解了子數組的概念後,不知道你有沒有了思路?是否感覺題目瞬間簡單了。
不就是在數組中找出所有的連續的k個元素的子數組,對於每個子數組求和,再除k與threshold比較。那麼有以下幾個問題:

  1. 如何找出一個數組所有的連續的k個元素的子數組?
    以我們正常人的思維首先想到的就是遍歷,雙層循環,第一層循環來表示子數組的開始元素所在位置,第二層循環從開始元素所在位置想後遍歷k個元素,不就能夠得到所有的k個元素的子數組。
  2. 對每個子數組求和再除k與threshold比較有沒有問題?是否可以簡單優化?
    作爲程序員應該知道整型數據進行除法,如果結果有小數的話,小數會丟失,從而產生誤差,所以看到整型除法我們要想辦法規避,可以想到除法變乘法;而且對於每個子數組來說,求和是必須的要求,但是每個子數組都要除k,再與threshold比較,很顯然可以不用每次除k,直接事先求出k乘threshold的值,再與子數組和比較,這樣省去了好多次除法,對代碼也算是一種優化。

好了,對於算法題,有了大概思路,我們就應該再草稿紙上或者腦子裏,將整個算法過程列出來,這樣寫代碼就是分分鐘的問題。以下是我的過程,小夥伴們要自己想哦。想完再來參考。

  1. 需要用到的數據(對應代碼數據聲明定義)
    (1)存放比較目標的變量 target,即k*threshold
    (2)數組長度len,遍歷數組時需要用到,
    (3)存放返回結果的變量num,記錄大於等於target的子數組數目
    (4)存放子數組和的變量sum,遍歷每個子數組前清0,遍歷時計算,遍歷完與target比較並更新num

  2. 對於需要初始化的部分數據進行初始化,以及這些數據的作用和使用
    (1)target = k*threshold
    (2)len = array.size()
    (3)num = 0
    (4)在每次遍歷前更新sum=0,遍歷時sum+=array[i]

  3. 遍歷數組
    雙層循環,外層循環指定子數組的開始位置,開始位置的範圍是0~len-k;內層循環對從開始位置向後的k個元素求和,求和完成後在外層循環對求和結果進行判斷處理,更新num。

  4. 返回結果

有了算法過程,代碼也就輕而易舉可以寫出來咯

代碼
class Solution {
public:
    int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        // 聲明
        int num = 0,sum;
        // 計算target
        int target = k*threshold;
        // 計算數組長度
        int len = arr.size();
        // 遍歷求和並比較大小
        for(int i=0;i<=len-k;i++){
            // 內層求當前以i開始的k個元素的和
            sum = 0;
            for(int j=i;j<i+k;j++){
                sum+=arr[j];
            }
            // 判斷sum是否滿足大於等於target的要求
            if(sum >= target){
                num++;
            }
        }
        return num;
    }
};
問題

通過計算複雜度我們發現,雙層循環一共要執行(len-k+1)*k次,時間複雜度爲O(n^2);空間複雜度由於我們使用的是常數個變量,所以爲O(1)。
提交代碼會發現,這種解法由於時間複雜度過高會出現超時的現象,如何解決?只能想辦法優化這個雙層循環了,看看能不能變成O(n)或者其它,如果不優化雙層循環的話,還是O(n^2),那就還是超時唄。

思路二

我們要想辦法優化雙層循環,仔細分析這個雙層循環,我們的目標是求子數組的元素之和,但是我們隱約感覺到每次求一個子數組的元素之和時好像用到了之前一次求的結果。如果我們能利用上前一次求和的結果來進行計算,會省去內層循環好多不必要的重複加法。
那麼我們就找前一次求和結果和這一次求和結果的關係咯,假設前一次求和結果是sum0,這一次求和結果是sum1,那麼經過分析可知sum1等於sum0減去一個開頭元素,再加上一個末尾元素。而且減去的這個開頭元素和增加的末尾元素在數組中相對位置不變,如果我們指定了添加的末尾元素,減去的開頭元素位置也就知道了。我們索性,以添加的末尾元素爲基準,進行數組遍歷,獲得每一次sum。這要求我們得有一個初始sum爲基準。
好了,問題解決了,只要更改思路一中求每個子數組元素之和的方式即可。

代碼
class Solution {
public:
    int numOfSubarrays(vector<int>& arr, int k, int threshold) {
        // 聲明
        int num = 0,sum=0;
        // 計算target
        int target = k*threshold;
        // 計算數組長度
        int len = arr.size();
        // 找到所有k個元素的子數組,對每個子數組進行判斷處理
        // 先找到第一個子數組,進行求和判斷
        for(int i=0;i<k;i++){
            sum+=arr[i];
        }
        if(sum >= target) num++;
        // 對於剩下的len-k個子數組,進行遍歷處理
        for(int i=k;i<len;i++){
            sum-=arr[i-k];
            sum+=arr[i];
            if(sum >= target) num++;
        } 
        return num;
    }
};
複雜度

通過分析,總共遍歷了有len次,所以時間複雜度從O(n^2)變成了O(n);空間複雜度不變,還是O(1)

總結

通過這道數組題目,我們可以獲得以下知識:

  1. 不管什麼數據結構,求連續的幾個元素問題,都可以想到將普通的暴力雙層循環改成單層循環。
  2. 對於代碼優化,從複雜度方面去分析代碼效率低下的主要問題,如本題的雙層循環,不改這個雙層循環肯定沒法優化,針對這個主要問題來進行想辦法解決。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章