前缀和相关leetcode

前缀和相关leetcode

前缀和即元素i前面的元素和,包括元素i。

No.724 寻找数组的中心下标

思路:如果数组为空,返回-1。创建一个前缀和数组sum,遍历nums,将元素i的前缀和放入sum[i]。如果sum的最后一个元素-sum的第一个元素等于0,返回0,表示中心下标是0。遍历sum,如果数组的最后一个元素-sum[i]等于sum[i-1],返回i。最后返回-1。

class Solution {
    public int pivotIndex(int[] nums) {
        if(nums == null || nums.length == 0)
            return -1;
        int[] sum = new int[nums.length];
        sum[0] = nums[0];
        for(int i = 1; i < nums.length; i++) {
            sum[i] = sum[i - 1] + nums[i];
        }
        if((sum[sum.length - 1] - sum[0]) == 0)
            return 0;
        for(int i = 1; i < sum.length; i++) {
            if(sum[i - 1] == (sum[sum.length - 1] - sum [i]))
                return i;
        }    
        return -1;
    }
}

No.560 和为K的子数组

思路:
  如果数组为空,返回0。
  创建一个哈希表,键是某个前缀和,值是该前缀和出现的次数。定义变量sum,表示前缀和。定义变量count,记录和k出现的次数。要计算子数组和为k的情况数,
  子数组和=元素i的前缀和-元素j的前缀和=k,但使用双层循环会超出时间限制。
  因此改为在遍历数组计算元素i的前缀和的时候,求出在元素i之前有多少个元素j的前缀和满足和元素i的前缀和的差为k,也就是元素i和多少个元素j之前的子数组的和为k,将该个数加入结果中。
  注意判断哈希表是否存在sum - k要写在将sum加入哈希表之前,否则如果k=0,就会将当前元素i的前缀和sum加进哈希表之后马上判断sum-0=sum,导致count++。
  遍历数组,计算元素i的前缀和,如果哈希表中有键sum-k(如果sum表示元素i的前缀和,sum-k表示元素j的前缀和,k就是元素i和元素j之间的连续元素的和,哈希表包含sum-k就表示有连续子数组的和为k),就将count+=sum-k对应的值。如果哈希表中有键sum,就将sum对应的值+1,否则将sum加入哈希表。

class Solution {
    public int subarraySum(int[] nums, int k) {
        if(nums == null || nums.length == 0)
            return 0;
        int sum = 0;//前缀和
        int count = 0;//子数组个数
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);//和为0的子数组有一个,用于统计nums中元素为k的情况
        for(int i = 0; i < nums.length; i++) {
            sum += nums[i];
            //子数组和 = 元素i的前缀和 - 元素j的前缀和 = k
            //转为寻找元素j的前缀和 = 元素i的前缀和 - k
            //也就是在当前计算到元素i的前缀和的情况下,在元素i之前有多少个元素j的前缀和满足和元素i的前缀和的差为k,将该个数加入结果中
            //也就是元素i和多少个元素j之前的子数组的和为k
            //注意判断哈希表是否存在sum - k要写在将sum加入哈希表之前,
            //否则如果k=0,就会将当前元素i的前缀和sum加进哈希表之后马上判断sum-0=sum,导致count++
            if(map.containsKey(sum - k)) {
                count += map.get(sum - k);
            }
            if(map.containsKey(sum)) {
                //将元素i的前缀和在哈希表中对应的出现次数+1
                map.put(sum, map.get(sum) + 1);
            } else {
                //将元素i的前缀和加入哈希表
                map.put(sum, 1);
            }
        }
        return count;
    }
}

No.930 和相同的二元子数组(和560几乎一样)

class Solution {
    public int numSubarraysWithSum(int[] A, int S) {
        if(A == null || A.length == 0)
            return 0;
        int sum = 0;//前缀和
        int count = 0;//和为S的子数组的个数
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);//和为0的子数组个数初始化为1,为了统计A中存在元素值为S的情况
        //子数组的和 = 元素i的前缀和 - 元素j的前缀和 = S
        //转为元素j的前缀和 = 元素i的前缀和 - S,也就是元素i之前存在多少前缀和和元素i的前缀和相差S的元素j
        for(int i = 0; i < A.length; i++) {
            sum += A[i];
            if(map.containsKey(sum - S)) {
                count += map.get(sum - S);
            }
            if(map.containsKey(sum)) {
                map.put(sum, map.get(sum) + 1);
            } else {
                map.put(sum, 1);
            }
        }
        return count;
    }
}

No.1248 统计优美子数组

思路:
  如果数组为空,返回0。创建一个哈希表,键是奇数个数,值是该奇数个数对应的子数组数。定义变量oddNum,表示奇数个数。定义变量count,记录奇数个数为k的子数组的数量。   要计算奇数个数k对应的子数组数,
  子数组中奇数个数=元素i的前缀数组的奇数个数-元素j的前缀数组的奇数个数=k,
  转为元素j的前缀数组的的奇数个数=元素i的奇数个数-k,
  也就是在计算当前元素i的前缀数组的奇数个数时,求出在元素i之前存在多少个元素j的前缀数组中的奇数个数和元素i的前缀数组中的奇数个数之差为k,
  也就是元素i和多少个元素j之间的子数组中的奇数个数为k,将该个数加入个数。
  遍历数组,如果元素i为奇数,则奇数个数oddNum++,表示元素i的前缀数组中的奇数个数+1。如果哈希表中包含oddNum-k,则将count+=oddNum-k对应的值,即对应的子数组个数。如果哈希表中包含oddNum,就将oddNum对应的值+1,表示该奇数个数对应的子数组数+1,否则将oddNum加入哈希表。

class Solution {
    public int numberOfSubarrays(int[] nums, int k) {
        if(nums == null || nums.length == 0)
            return 0;
        int oddNum = 0;//前缀数组的奇数个数
        int count = 0;//优美子数组个数
        Map<Integer, Integer> map = new HashMap<>();//每一种奇数个数对应的优美子数组数
        map.put(0, 1);
        //子数组中奇数个数 = 元素i的前缀数组中的奇数个数 - 元素j的前缀数组中的奇数个数 = k
        //转为元素j的前缀数组中的奇数个数 = 元素i的前缀个数中的奇数个数 - k
        //也就是计算当前元素i的奇数个数时,判断元素i前有多少元素j的奇数个数和元素i的奇数个数之差为k
        //也就是元素i和多少元素j之间的子数组中的奇数个数为k
        for(int i = 0; i < nums.length; i++) {
            //如果当前元素i是奇数,则元素i的前缀数组中的奇数个数+1
            if(nums[i] % 2 != 0)
                oddNum++;
            if(map.containsKey(oddNum - k)) {
                count += map.get(oddNum - k);
            }
            if(map.containsKey(oddNum)) {
                map.put(oddNum, map.get(oddNum) + 1);
            } else {
                map.put(oddNum, 1);
            }
        }
        return count;
    }
}

No.974 和可被K整除的子数组

思路:
  如果数组为空或除数K为0,返回0。定义变量sum表示前缀和,变量count表示能被K整除的子数组的个数。创建哈希表,存储余数和对应的子数组个数。
  要求能被K整除的子数组的个数,
  子数组的和%K=(元素i的前缀和-元素j的前缀和)%K=元素i的前缀和%K-元素j的前缀和%K=0,
  即元素i的前缀和%K=元素j的前缀和%K,
  也就是说元素i和元素j之间的子数组的和可以整除K等价于元素i的前缀和模K的余数和元素j的前缀和模K的余数相等。
  也就是在计算元素i的前缀和时,求得该前缀和模K的余数,然后再求出再在元素i之前有多少个元素j的前缀和模K的余数和元素i模K的余数相等,也就求得了元素i和多少个元素j之间的子数组的和可以整除K。
  遍历数组,求元素i的前缀和,如果该前缀和为负数,先加上K直到转为正数。然后将sum模K取余数(前缀和sum表示元素i的前缀和,),如果哈希表中包含该余数,将count+=该余数对应的值,也就是该余数对应的子数组的个数,然后将余数对应的子数组个数+1,如果不包含该余数,将该余数加入哈希表。

class Solution {
    public int subarraysDivByK(int[] A, int K) {
        if(A == null || A.length == 0 || K == 0)
            return 0;
        int sum = 0;//前缀和
        int count = 0;//可被K整除的子数组个数
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        //子数组的和 = 元素i的前缀和 - 元素j的前缀和
        //子数组的和 % K = (元素i的前缀和 - 元素j的前缀和) = 元素i的前缀和 % K - 元素j的前缀和 % K = 0
        //元素i的前缀和 % K = 元素j的前缀和 % K
        //也就是只要元素i的前缀和模K的余数等于元素j的前缀和模K的余数相等,元素i和元素j之间的子数组的和就可以整除K
        //也就是在求元素i的前缀和后,求出该前缀和模K的余数,然后求在元素i之前有多少个元素j的前缀和模K的余数和元素i的前缀和模K的余数相等
        //就相当于元素i和多少个元素j之间的子数组的和可以整除K
        //因此将每个元素的前缀和模K的余数当作键存进哈希表,
        for(int i = 0; i < A.length; i++) {
            sum += A[i];
            while(sum < 0)
                sum += K;
            int key = sum % K;
            if(map.containsKey(key)) {
                count += map.get(key);
            }
            if(map.containsKey(key)) {
                map.put(key, map.get(key) + 1);
            } else {
                map.put(key, 1);
            }
        }
        return count;
    }
}

No.523 连续的子数组和

思路:
  如果数组为空或除数k为0,返回false。定义变量sum记录每个元素的前缀和。创建哈希表存储每个元素的前缀和模k的余数和该元素的索引。
  将(0,-1)加入哈希表,这样之后遇到模k余数为0的元素就相当于找到了两个元素的都为0,只要子数组长度大于等于2,就可以返回true了;不设置的话需要在nums中存在两个元素的余数为0才可以返回true。
  首先求是否存在子数组的和可以被k整除。和上一题思路一样。
  子数组的和%k=(元素i的前缀和-元素j的前缀和)%k=元素i的前缀和%k-元素j的前缀和%k=0,
  即元素i的前缀和=元素j的前缀和,也就是说元素i和元素j之间的子数组的和可以被k整除等价于元素i的前缀和模k的余数等于元素j的前缀和模k的余数。
  遍历数组,求元素i的前缀和,然后得到模k的余数,如果哈希表中包含这个余数,说明在元素i之前有元素j的余数和元素i的余数相等,如果元素j和元素i之间的子数组长度>1,就可以返回true了(这里大于1是因为在之前将(0,-1)加入了哈希表,子数组长度从-1的位置开始计算,但实际上的数组是从0开始的,所以计算出来的长度大于1,相当于实际上子数组的长度>0)。如果哈希表中不包含这个余数,就将该余数和当前元素i的下标i加入哈希表。

class Solution {
    public boolean checkSubarraySum(int[] nums, int k) {
        if(nums == null || nums.length == 0 || k == 0)
            return false;
        int sum = 0;//前缀和
        Map<Integer, Integer> map = new HashMap<>();//每个元素的前缀和模k的余数 - 元素索引
        //初始化余数为0对应的元素索引为-1,这样遇到第一个前缀和可以整除k的元素(也就是余数为0)时,只要其和-1之间的子数组长度>=2就可以返回true了
        //不初始化的话,需要有两个余数为0的元素才可以返回true
        map.put(0, -1);
        //子数组的和 % k = (元素i的前缀和 - 元素j的前缀和) % k = 元素i的前缀和 % k - 元素j的前缀和 % k = 0
        //满足元素i的前缀和 % k = 元素j的前缀和 % k,元素i和元素j模k的余数相等 等价于 元素i和元素j之间的子数组的和可以整除k
        for(int i = 0; i < nums.length; i++) {
            sum += nums[i];
            int key = sum % k;
            //大于1时因为将(0,1)存进了map,会从-1索引开始计算子数组长度,大于1才算是在子数组长度大于等于2,
            //设为>1,如i=1,1-(-1)=2>1,实际上子数组只有下标0和1上的元素,刚好长度等于2
            //而如果设为>0,i=0,0-(-1)=1>0,但是实际上子数组只有下标0的元素,长度=1<2
            if(map.containsKey(key)) {
                //如果哈希表包含该余数,说明元素i之前有元素j的余数和元素i相等,也就是元素i和元素j之间存在子数组的和可以被k整除
                //如果该子数组的长度至少为2,则返回true
                if(i - map.get(key) > 1) {
                    return true;
                }
            }
            else {
                map.put(key, i);
            }
        }
        return false;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值