前缀和 - wenzhoullq/leetcode GitHub Wiki

一维前缀和

关键词

连续

分析

先构建前缀和数组

      int len=nums.length;
      int[] sum=new int[len+1];
      for(int i=1;i<=len;i++){
            sum[i]=sum[i-1]+nums[i-1];
     }

思考这么两个问题

  • 问题 1

为什么sum数组取的是len+1?

前缀和的目的是为了能够O(1)的方式计算某一段连续子序列的和,如果我想O(1)计算[i,j]之间的和,注意这里的i和j指的是arr[]的,那么即sum[j+1]-sum[i]

而如果我想计算从0个开始的子数组之和,必须引入一个sum[0],这样就保证任意子数组之和的公式都是sum[i]-sum[j],归根到底,sum[0]是哨兵

  • 问题 2

前缀和数组构造需求有几种?

  1. 最简单求和的前缀和数组
for (int i = 1; i <= len; i++) sum[i] = sum[i - 1] + nums[i - 1];
  1. 一段子序列中变量的个数前缀和数组
for (int i = 1; i <= len; i++) sum[i] = sum[i - 1] + (nums[i - 1] == 1 ? 1 : -1);

3.一段子序列的和为k的倍数

for(int i=1;i<=len;i++) sum[i]=((sum[i-1]+nums[i-1])%k+k)%k;

4.字段子序列的和的余数是mod

    for(int i=1;i<=nums.length;i++){
            long temp=(sum[i]-mod)%p;//与上面的处理不同的是,余数的处理是在这里处理的
            if(m.containsKey(temp)) ans=Math.min(i-m.get(temp),ans);
             m.put(sum[i]%p,i);
        }

5.dfs前缀和数组

     int dfs(TreeNode root){
         list.add(root.val);
         dfs(root.left);
         dfs(root.right);
         list.removeLast();
        
    
}

6.重复子序列

            for(int i=1;i<=n;i++){
                sum1[i]=sum1[i-1]+(s[i-1]==strs[i%3]?0:1);//注意这个括号,比较重要
                sum2[i]=sum2[i-1]+(s[i-1]==strs[(i+1)%3]?0:1);
                sum3[i]=sum3[i-1]+(s[i-1]==strs[(i+2)%3]?0:1);
            }

在构建好前缀和数组后,对前缀和数组进行使用,对前缀和数组的使用方式有两种

  • 非map/非精准求值
    for(int i=1;i<=len;i++){
     for(int j=0;j<i;j++){
        if( pre[i]-pre[j] 满足条件)  //处理
         } 
  }

容易分析到,时间复杂度为O(n²),但是一些长度较长的,如length<=1e6,显然会超时,我们必须得使用O(n)来做题

既然和计算能拿来做,异或计算也可以拿来做

  • hashmap/hashSet/精准求值

使用hashmap有两种需求,一种是对长度的需求(最大长度),另一种是对数量的需求(最大数量),hashset适用于求不交叉的子数组个数

  1. 最大长度
  • hashmap
        Map<Integer, Integer> m = new HashMap<>();
        m.put(sum[0],0);//哨兵
        for (int i = 1; i <= len; i++) {
            if(m.containsKey(sum[i])) ans=Math.max(ans,i-m.get(sum[i]));
            else  m.put(sum[i],i);//如果是最短长度,那么else忽略
        }
  1. 最大数量
  • hashmap
        HashMap<Integer,Integer> m=new HashMap<>();
        m.put(sum[0],1);//哨兵,注意两种不同的哨兵建立方式
        for(int i=1;i<=len;i++){
            ans+=m.getOrDefault(sum[i]-k,0);
            m.put(sum[i],m.getOrDefault(sum[i],0)+1);
        }
  • hashset
        HashSet<Integer> s=new HashSet<>();
        s.add(0);//哨兵
        int ans=0;
        for(int i=1;i<=nums.length;i++){
            if(s.contains(sum[i]-target)) {
                s.clear();//清除是关键
                ans++;
                s.add(sum[i]);
                continue;
            }
            s.add(sum[i]);
        }
        return ans;
    }
}

容易分析的是,hashmap的方式是O(n),与暴力相比快太多,是不是意味着非map的方式无用?

并非如此,hashmap是精准求值,而暴力的方式可以对不精准的目标,如某子序列之和>某值特别适用;如果暴力超时的话,应该存在别的优化方式

裸题

题目

238. 除自身以外数组的乘积

303. 区域和检索 - 数组不可变

724. 寻找数组的中心下标

1248. 统计「优美子数组」

1310. 子数组异或查询

1480. 一维数组的动态和

1524. 和为奇数的子数组数目

2845. 统计趣味子数组的数目

变型sum数组构造

题目

396. 旋转函数 如果是旋转类的前缀和,可以将写一个两倍长的sum[]来替代前找的动作

面试题 17.05. 字母与数字

112. 路径总和

113. 路径总和 II

437. 路径总和 III (dfs前缀和)

523. 连续的子数组和

525. 连续数组

974. 和可被 K 整除的子数组 余数,但是余数中出现负数,还要对负数处理(+k)

1248. 统计「优美子数组」

C. Yet Another Walking Robot-灵茶2023.4.12

非hashmap型

题目

862. 和至少为 K 的最短子数组 这题暴力会超时,使用的是滑动窗口,但是滑动窗口的前提是单调,于是用单调队列维持单调

HashMap长度型

题目

523. 连续的子数组和

525. 连续数组

1371. 每个元音包含偶数次的最长子字符串

面试题 17.05. 字母与数字

数量型

  • hashmap

题目

437. 路径总和 III

560. 和为 K 的子数组

930. 和相同的二元子数组

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

  • hashset

1546. 和为目标值且不重叠的非空子数组的最大数目

多重for循环

多重for循环往往是针对前缀和段排列顺序有要求/多个前缀和段

题目

1442. 形成两个异或相等数组的三元组数目

1588. 所有奇数长度子数组的和

1749. 任意子数组和的绝对值的最大值 (求连续和的最大值有贪心的解法,同时这题是多重for循环的前缀和,但是可以通过优化的方式把O(n²)降低到O(1))

1744. 你能在你最喜欢的那天吃到你最喜欢的糖果吗? 关键点:只有干完XXX才能后续步骤,前缀和思想,用除的方式优于乘

前缀和变型

表面上不是前缀和,但是转换后成为前缀和

2106. 摘水果 表面上不是前缀和,但是左右移动可以完全成(左2+右/右2+左)

前缀和+二分

分析

前缀和+二分与二分+前缀和不同,一个是前缀和为导向,对前缀和查找需要二分;另一个是二分为导向,对前缀和

题目

528. 按权重随机选择 权重用前缀和来处理

2055. 蜡烛之间的盘子 值得注意的是二分求最接近的下标

二分+前缀和

分析

以二分为切入点,前缀和辅助

1838. 最高频元素的频数

前缀和+单调栈

题目

1124. 表现良好的最长时间段 这里是后者大于前者的最大宽度单调栈模板

1856. 子数组最小乘积的最大值这个是类似于求最大矩阵面积的单调栈模板

前缀和+状态压缩

前缀和的状态压缩有两种题型,第一种题型是该区间的奇偶个数,使用^运算;另一种是该区间的存在型,使用|运算,通常|运算伴随着去重 如果问存在一个奇数(为什么是奇数,因为为0的话可能是偶数出现也可能是0次出现),需要对前面存的情况的某一位^1

for(int j = 0 ; j < 10; j++){
                int t = temp^(1<<j);
                if(m.containsKey(t))ans =Math.max(ans,);
            }

题目

  • 奇偶个数

1177. 构建回文串检测

1371. 每个元音包含偶数次的最长子字符串

1542. 找出最长的超赞子字符串

1915. 最美子字符串的数目 题目说只涉及到前10个字母,因此需要联想到状态压缩

  • 存在性

1930. 长度为 3 的不同回文子序列

前缀和的运用

题目

1685. 有序数组中差绝对值之和

1829. 每个查询的最大异或值

1991. 找到数组的中间位置

2017. 网格游戏 自己最大并非别人最小,别人最小才是目的

二维面积和

和arr[][]下标比大1 O(1)时间求面积,可以用于O(1)的查询某个区域内有无标志

模板

//初始化
           int leny=,lenx=;
           int[][] sum=new int[leny+1][lenx+1];
           for(int i = 1; i <= leny;i++)
               for(int j = 1; j<=lenx ;j++)
                  sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+martix[i-1][j-1];
        int x1+1,y1+1,x2+1,y2+1;//x1,y1是左上角的坐标+1,x2,y2是右下角的坐标+1
        area=sum[y2][x2]-sum[y1-1][x2]-sum[y2][x1-1]+sum[y1-1][x1-1];
       

不仅仅是求和,求^的二维矩阵也可以使用

           int leny=xxx,lenx=xxx;
           int[][] sum=new int[leny][lenx];
           sum[i][j]=sum[i-1][j]^sum[i][j-1]^sum[i-1][j-1]^martix[i-1][j-1];
           area=sum[y2][x2]^sum[y1-1][x2]^sum[y2][x1-1]^sum[y1-1][x1-1];

二维前缀和-和运算

题目

304. 二维区域和检索 - 矩阵不可变

1738. 找出第 K 大的异或坐标值

363. 矩形区域不超过 K 的最大数值和

661. 图片平滑器 (值得注意点的是,这个是以固定长度的矩阵去横扫,并且边界上也会横扫,那么会出现越界问题,如何处理越界呢,用Math.max(0,i-1);

1074. 元素和为目标值的子矩阵数量

1314. 矩阵区域和

1292. 元素和小于等于阈值的正方形的最大边长

2132. 用邮票贴满网格图O(1)求标志位

二维前缀和-异或运算

题目

1738. 找出第 K 大的异或坐标值

左右前缀和

题目

1769. 移动所有球到每个盒子所需的最小操作数

子数组

子数组往往用到前缀和,但是需要等价转换

题目

2488. 统计中位数为 K 的子数组

⚠️ **GitHub.com Fallback** ⚠️