前缀和相关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;
}
}