前缀和题目
前缀和技巧适用于快速、频繁地计算一个索引区间内的元素之和。
前缀和
寻找数组的中心下标
java
class Solution {
public int pivotIndex(int[] nums) {
//思路:1.求出前缀和 2.遍历前缀和
int sz=nums.length;
int[]preSum=new int[sz+1];//preSum[i]:前i个的和
for(int i=1;i<=sz;i++){
preSum[i]=preSum[i-1]+nums[i-1];
}
for(int i=1;i<=sz;i++){
int leftSum=preSum[i-1];
int rightSum=preSum[sz]-preSum[i];
if(leftSum==rightSum){
//找到了
return i-1;
}
}
return -1;
}
}
前缀积
除自身以外数组的乘积
java
class Solution {
public int[] productExceptSelf(int[] nums) {
int sz=nums.length;
//思路:求出前缀积和后缀积
int[]preMuti=new int[sz+2];//preMuti[i]:前一个的累积
int[]backMuti=new int[sz+2];
Arrays.fill(preMuti,1);
Arrays.fill(backMuti,1);
for(int i=1;i<=sz;i++){
preMuti[i]=preMuti[i-1]*nums[i-1];
}
for(int i=sz;i>=1;i--){
backMuti[i]=backMuti[i+1]*nums[i-1];
}
int[]res=new int[sz];
for(int i=1;i<=sz;i++){
res[i-1]=preMuti[i-1]*backMuti[i+1];
}
return res;
}
}
前缀和+哈希表
涉及到和为 xxx 的子数组 ,就是要考察 前缀和技巧 和 哈希表 的结合使用了。
连续数组
java
class Solution {
public int findMaxLength(int[] nums) {
//可以转换成求子数组和为xxx的最长子数组
//1.把0变成-1,求子数组和为0的最长子数组长度
int sz=nums.length;
for(int i=0;i<sz;i++){
if(nums[i]==0){
nums[i]=-1;
}
}
//2.求前缀和
int[]preSum=new int[sz+1];
for(int i=1;i<=sz;i++){
preSum[i]=preSum[i-1]+nums[i-1];
}
//3.在前缀和数组中,寻找preSum[i]==preSum[j],i和j越远越好
//用到了哈希表,在遍历前缀和数组时,第一次遇到就进去,第二次遇到就更新
//i越小越好
int res=0;//记录最长的子数组
HashMap<Integer,Integer>map=new HashMap();//key是值,value是索引
for(int i=0;i<=sz;i++){
int cur=preSum[i];
if(!map.containsKey(cur)){//说明是第一次出现
map.put(cur,i);
}else{
//前面出现过cur
res=Math.max(res,i-map.get(cur));
}
}
return res;
}
}
连续的子数组和
寻找 i, j 使得 (preSum[i] - preSum[j]) % k == 0 且 i - j >= 2。
另外,(preSum[i] - preSum[j]) % k = = 0 其实就是 preSum[i] % k = = preSum[j] % k。
java
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
//先求出前缀和 前缀和模上k
int sz=nums.length;
int[] preSum=new int[sz+1];
for(int i=1;i<=sz;i++){
preSum[i]=(preSum[i-1]+nums[i-1])%k;
}
//key是出现的值,value是索引
HashMap<Integer,Integer> map=new HashMap();
map.put(0,0);
for(int i=1;i<=sz;i++){
int cur=preSum[i];
if(map.containsKey(cur)){
//前面出现了cur,长度也>=2
if(i-map.get(cur)>=2){
return true;
}
}else{
map.put(cur,i);
}
}
return false;
}
}
和为k的子数组
java
class Solution {
public int subarraySum(int[] nums, int k) {
int sz=nums.length;
//前缀和
int[]preSum=new int[sz+1];
for(int i=1;i<=sz;i++){
preSum[i]=preSum[i-1]+nums[i-1];
}
int counter=0;
//key是值,value是出现的个数
HashMap<Integer,Integer> map=new HashMap();
map.put(0,1);
for(int i=1;i<=sz;i++){
int cur=preSum[i];
if(map.containsKey(cur-k)){
//map中包含cur-k
counter+=map.get(cur-k);
}
map.put(cur,map.getOrDefault(cur,0)+1);
}
return counter;
}
}
和可被k整除的子数组
我的错误思路:1.先求出前缀和数组,然后每个值对k取模 2.找出preSum[i]==preSum[j] 的个数。
出现的问题:当nums为{-1,2,9} ,k=2,preSum={0,-1,1,0},所以答案为1{-1,2,9},漏了{2}。
我们求
preSum[ nums[j...i] ] % k= =0 ,转换成
(preSum[nums[0...i]]-preSUm[nums[0...j]] )%k ==0 ,转换成:
preSum[nums[0...i]]%k= =preSum[nums[0...j]]%k .回到上面的例子,-1和1也是同余关系,我们应该把负数变成正数即可。
java
import java.util.HashMap;
class Solution {
public int subarraysDivByK(int[] nums, int k) {
int sz=nums.length;
int[]preSum=new int[sz+1];
for(int i=1;i<=sz;i++){
preSum[i]=preSum[i-1]+nums[i-1];
}
for(int i=0;i<=sz;i++){
preSum[i]%=k;
}
//在preSum中找一样的
int counter=0;
//key表示同余的值全是整数,如果是负数就变成整数,value是个数
HashMap<Integer,Integer> map=new HashMap();
map.put(0,1);
for(int i=1;i<=sz;i++){
int cur=preSum[i]%k;
if(cur<0){
cur+=k;
}
if(map.containsKey(cur)){
counter+=map.get(cur);
}
map.put(cur,map.getOrDefault(cur,0)+1);
}
return counter;
}
}
表现良好的最长时间段
java
import java.util.HashMap;
class Solution {
public int longestWPI(int[] hours) {
//可以把>8小时的一天赋值为1,<=8的为-1
//求和>0的最长数组
int sz=hours.length;
for(int i=0;i<sz;i++){
if(hours[i]>8){
hours[i]=1;
}else{
hours[i]=-1;
}
}
//前缀和
int[]preSum=new int[sz+1];
for(int i=1;i<=sz;i++){
preSum[i]=preSum[i-1]+hours[i-1];
}
//key=前缀和的值,value是索引
HashMap<Integer,Integer> map=new HashMap();
map.put(0,0);
int maxPeriod=0;
//遍历前缀和数组
for(int i=1;i<=sz;i++){
int curPreSum=preSum[i];
if(curPreSum>0){
//curPreSum为正数,说明前面[0,i-1]是良好时间段
maxPeriod=Math.max(maxPeriod,i);
}else{
//curPreSum为负数,想要最长的时间段
if(map.containsKey(curPreSum-1)){
//找到了
maxPeriod=Math.max(maxPeriod,i-map.get(curPreSum-1));
}
}
//如果当前的值前面没有出现过,就记录下来,出现过就别管,前缀索引越小越好
if(!map.containsKey(curPreSum)){
map.put(curPreSum,i);
}
}
return maxPeriod;
}
}