「数据结构与算法」动态规划学习笔记:前缀和

前缀和是一种查询数组中任意区间的元素的和的数据结构,这里数组给定之后就不变了。针对这个不变的数组,前缀和用于多次查询区间 [i, j] 上元素的和。

对于动态规划而言,前缀和的意义主要有两点:

  • 一维和二维前缀和的推导,分别用到了单串和矩阵中最经典的状态设计以及状态转移
  • 在一些更复杂的动态规划问题中,状态转移的时候需要依赖区间和,因为状态转移是非常频繁的操作,因此必须高效地求区间和才能使得状态转移的时间复杂度可接受,此时就必须用到前缀和了

一些问题需要前缀和与其它数据结构配合来解决,也有两类:

  • 先预处理出前缀和数组,这一步是动态规划,然后在前缀和数组上用其它数据结构解决
  • 还是按照动态规划的方式求前缀和,也需要额外的数据结构维护前缀和,但不是预处理好前缀和数组之后再用数据结构计算,而是每求出一个前缀和,就更新一次数据结构并维护答案

前缀和 简介

给定长度 n 的序列 a (a[0], a[1], ..., a[n-1]) ,给每个前缀求一次和, S[0] = 0 , S[i] = a[0] + a[1] + ... + a[i - 1] 。这些前缀和维护在一个长度 n + 1 数组 S 里,称为前缀和数组

有两类在数组 a 上的求和需求:

  1. 前缀和:求 a[0..i] 的和
  2. 区间和:求区间 a[L, R] 的和

对于第 1 种前缀和 S[i + 1] 刚好就是答案,因为这就是前缀和的定义。 S[i+1] = a[0] + a[1] + ... + a[i]

对于第 2 种区间和,如果已经有了前缀和数组,两个前缀和 S[R + 1] S[L] 的差刚好就是 [L, R] 上的区间和。已有前缀和数组之后,这一步操作就是 O(1) 的。

定义 sums[k] [0..k-1] 的和,其中 sums[0] 表示数组中没有数字被选中, sums[1] 表示只选中第一个数 nums[0]。 预先计算 0 ~ k (0<=k<=n-1) 的和,这一系列的和都是从 0 开始的,因此称为前缀和。公式如下:

  1. k = 0: sum(0, 0) = 0
  2. 1 <= k <= n: sum(0, k) = nums[0] + nums[1] + ... + nums[k - 1]

此后的区间查询都可以利用公式:

  • sum(i, j) = sums(0, j + 1) - sum(0, i)

线性动态规划中最基础的一种是单串 dp[i] ,并且只与子问题 i - 1 有关,即 dp[i] = f(dp[i-1]) 前缀和就是这种情况, sums[i] 只与 sums[i-1] 有关。推导前缀和数组的过程是 O(N) 的,如果区间和的查询次数达到了 O(N) 那么计算区间和的时间复杂度是摊销 O(1) 的。

数据结构 维护 前缀和

用动态规划的方式推 sums[i] 的时候,在某些题型中计算完 sums[i] 后需要查询以前算过的结果以此来计算某种指标(例如小于、大于、等于某个数值等),此时就需要用数据结构来将前面的计算结果维护起来,以便高效查询。

前缀和维护在数据结构中,以便于后续的多次查询,最常见的是维护在哈希表 HashMap中。

HashMap 维护 前缀和
Key:前缀和(状态)的值
Value:第一次出现时的索引

经典问题① a[0], a[1], ..., a[n - 1] 上有没有一个区间,其和为 target

关键词:[有没有][区间][等于]

计算前缀和数组 sums[i] 。当扫描到 i 时, a[0], a[1], ..., a[i - 1] 的前缀和都已经求过了,所以可以在计算的过程中,将前缀和维护在数据结构中,以便于后续的多次查询。本题在之后要查询前缀和的值是否存在,因此维护在HashSet里。

求完当前值 a[i] 对应的前缀和 S[i+1] , 在插入到HashSet之前先考虑: S[i+1] - target HashSet中是否出现过。

  • 如果出现:说明存在以 i 结尾的某个区间,和为 target ,则找到答案
  • 如果不出现:说明不存在以 i 结尾的区间,和为 target ,则继续枚举 i + 1

经典问题② a[0], a[1], ..., a[n - 1] 上有多少个区间,其和为 target

关键词:[多少个][区间][等于]

整体解题思路与问题①相似。

区别在于题目要求统计个数,所以需要使用HashMap来替代HashSet并以此维护多个 Key(和的值) - Value(该和的值出现的个数) 键值对。

LeetCode 560 和为 K 的子数组
https://leetcode-cn.com/probl...

经典问题③ a[0], a[1], ..., a[n - 1] 上有多少个区间,其和大于、小于 target

关键词:[多少个][区间][大于、小于]

LeetCode 327 区间和的个数
https://leetcode-cn.com/probl...

经典问题④ 一棵树上有没有一个路径,其和为 target

关键词:[有没有][树][等于]

LeetCode 437 路径总和 III
https://leetcode-cn.com/probl...

经典问题⑤ 矩形区域内有多少个子矩阵,其和大于、小于或等于 target

关键词:[有没有][多少个][矩形区域][大于、等于、小于]

LeetCode 363 矩形区域不超过 K 的最大数值和
https://leetcode-cn.com/probl...

LeetCode 1074 元素和为目标值的子矩阵数量
https://leetcode-cn.com/probl...

面试题 17.24 最大子矩阵
https://leetcode-cn.com/probl...

对于矩阵问题,可以优先考虑将二维矩阵压缩成一维数组,然后参考问题①②③的方式使用前缀和进行求解。


山庄的铁匠
18 声望11 粉丝