zoukankan      html  css  js  c++  java
  • 【前缀和】和为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;
        }
    }
    
  • 相关阅读:
    UWP 视觉状态管理 VisualStateManager
    RainbowEight
    Thread.Sleep在WinRT中---uwp应用
    fsasefa
    那年坑系列之好多坑_by二卷
    deep Learning Introduction_1_by二卷
    笔记本无线联网时VirtualBox的联网问题_by二卷
    Linux入门篇之目录结构_by二卷_待补充
    Python核心编程Ⅱ章3Python基础_by二卷
    数据挖掘导论章3探索数据_by二卷
  • 原文地址:https://www.cnblogs.com/gzshan/p/12979555.html
Copyright © 2011-2022 走看看