【前綴和】和為K、和可被K整除的子數組


  連續子數組問題是算法中經常可以見到的一類題目,通過幾個典型的題目分析,可以發現這類題目主要分為兩大類,其解題思路通過最簡單的子串枚舉(枚舉所有的子串起點和終點)來暴力解決大都不難,但是如果考慮到對空間和時間的要求,其解答就需要一定的算法技巧。

  • 子數組和問題(前綴和+哈希表)
  • 子數組最值問題(多階段決策過程最優化問題,動態規划)

【子數組】子數組和問題(前綴和)

  一看到子數組和,有必要先對前綴和思想進行一些考慮。前綴和指的是:數組 第 0 項 到 當前項 的 總和,類似於我們在數學中學到的數列的前n項和

  如果用一個數組 pre[] 表示:pre[i]=nums[0]+nums[1]+···+nums[i]

  前綴和的優勢在於:數組中的某一項可以表示為相鄰前綴和之差:nums[i]=pre[i]-pre[i-1],因此,從 i 到 j 范圍子數組和就可以表示為:nums[i]+nums[i+1]+···+nums[j]=pre[j]-pre[i-1],這一關系就為求解子數組和問題提供了數學依據。


560、和為K的子數組

  題目描述:給定一個整數數組和一個整數 k,你需要找到該數組中和為 k 的連續的子數組的個數。

示例:
輸入:nums = [1,1,1], k = 2
輸出: 2 , [1,1] 與 [1,1] 為兩種不同的情況。

  解題思路:

  解法一:暴力枚舉

  暴力枚舉子數組的起點 i 和終點 j,對其中的元素進行求和,得到和判斷是否等於k,時間復雜度為O(n^3),但是對於子數組累積求和可以在遍歷的過程中同步進行記錄,因此,暴力法的時間復雜度最多可以優化到O(n^2)

  解法二:前綴和+哈希表

  正如前面提到的前綴和的優勢,從 i 到 j 范圍子數組和可以表示為:nums[i]+nums[i+1]+···+nums[j]=pre[j]-pre[i-1],那么從 i 到 j 范圍子數組和為K這個條件可以表示為:pre[j]-pre[i-1]=k.

  因此,這就可以得到pre[i-1]=pre[j]-k,所以考慮以 j 結尾的和為 k的連續子數組個數時,只要統計有多少個前綴和為pre[j]-kpre[i] 即可。通過建立哈希表,以前綴和作為鍵,該和出現的次數作為值,在遍歷數組時,一邊統計前綴和,一邊從哈希表中得到對應的次數,從而得到最后的答案,具體可以參見代碼實現。

  代碼實現

//解法一:暴力枚舉,時間復雜度O(n^2),空間復雜度O(1)
class Solution {
    public int subarraySum(int[] nums, int k) {
        int count=0;
        for(int i=0;i<nums.length;i++){
            int sum=0;
            for(int j=i;j<nums.length;j++){  //從i到j這個子數組,累積求和
                sum+=nums[j];
                if(sum==k)
                    count++;
            }
        }
        return count;
    }
}

//解法二:前綴和+哈希表,時間復雜度O(n),空間復雜度O(n)
class Solution {
    public int subarraySum(int[] nums, int k) {
        if(nums==null || nums.length==0)
            return 0;
        
        Map<Integer,Integer> map=new HashMap<>();  //<前綴和,次數>
        map.put(0,1);  //注意這是必要的,這可以保證不漏掉只有一個元素的子數組

        int count=0,pre=0;
        for(int i=0;i<nums.length;i++){
            pre+=nums[i];
            if(map.containsKey(pre-k))  //找pre-k出現的次數,pre-k
                count+=map.get(pre-k);
            map.put(pre,map.getOrDefault(pre,0)+1);
        }
        return count;
    }
}

974、和可被K整除的子數組

  題目描述:給定一個整數數組 A,返回其中元素之和可被 K 整除的(連續、非空)子數組的數目。

示例:

輸入:A = [4,5,0,-2,-3,1], K = 5
輸出:7
解釋:
有 7 個子數組滿足其元素之和可被 K = 5 整除:
[4, 5, 0, -2, -3, 1], [5], [5, 0], [5, 0, -2, -3], [0], [0, -2, -3], [-2, -3]

  解題思路:

  可以看到本題和上一題基本相同,不同之處僅僅在於上一題為和為K,而這里是和可以被K整除。其基本解題思路類似,但是需要用到一個數學上的同余定理:給定一個正整數m,如果兩個整數a和b滿足a-b能夠被m整除,即(a-b)/m得到一個整數,那么就稱整數a與b對m同余。

  解法一:暴力枚舉

  和上題類似,枚舉所有子數組,求和后判斷是否能被K整除即可,時間復雜度同樣為O(n^2)。

  解法二:前綴和+哈希表

  從 i 到 j 范圍子數組和可以被K整除這個條件可以表示為:pre[j]-pre[i-1] mod K == 0,根據我們提到的同余定理,只需要滿足pre[j] mod K == pre[i-1] mod K,就可以滿足題意。

  因此,可以考慮對數組進行遍歷,在遍歷同時統計答案。當我們遍歷到第 i 個元素時,我們統計以 ji 結尾的符合條件的子數組個數。然后維護一個以前綴和模 K 的值為鍵,出現次數為值的哈希表 ,在遍歷的同時進行更新。這樣類似上一題進行維護和計算即可得到結果。

  需要注意的是:不同的語言負數取模的值不一定相同,有的語言為負數,如Java 取模的特殊性,當被除數為負數時取模結果為負數,需要糾正。這是因為比如:k是2,序列是-3 4 9 ,那么模如果不是正數 ,會分別是 -1 1 0 ,而 -1 和 1之間剛好是距離k的,卻不被統計,這就會漏掉子數組為2的這個答案,糾正的方法是:(pre%K+K)%K

  代碼實現

class Solution {
    public int subarraysDivByK(int[] A, int K) {
        //哈希表+前綴和,時間復雜度O(n),空間復雜度O(n)
        //兩個數模K的結果相等, 其差能被K整除
        if(A==null || A.length==0)
            return 0;
        Map<Integer,Integer> map=new HashMap<>();  //<前綴和,次數>
        map.put(0,1);

        int pre=0,res=0;
        for(int i=0;i<A.length;i++){
            pre+=A[i];  //求前綴和
            int temp=(pre%K+K)%K; //注意 Java 取模的特殊性,當被除數為負數時取模結果為負數,需要糾正
            if(map.containsKey(temp))
                res+=map.get(temp);
            map.put(temp,map.getOrDefault(temp,0)+1);
        }
        return res;
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM