【数据结构与算法】同余计算 哈希表与前缀和问题特征和模板化思路
加减乘负的类同余计算
加乘模情况
- 两数和模 :
(a + b) mod m = (a mod m + b mod m) mod m
- 乘积模 :
(a * b) mod m = ((a mod m) * (b mod m)) mod m
加模证明如下 :
a = q1 * m + r1 , b = q2 * m + r2
- 则
a mod m = r1
,b mod m = r2
(a + b) mod m = ((q1 + q2) * m + (r1 + r2)) mod m = (r1 + r2) mod m = (a mod m + b mod m) mod m
结论 : 两数和模或乘积模与其非模本部无关 .
减模零情况
减模零即 ( a - b ) mod m = 0
. 记作 a ≡ b ( mod m )
数学范围内的模数通常不小于 0 , Java 中取模 % 操作得到的模数的符号与被模数相同 , 负数取模有 : 首先取得负数绝对值的模数 , 模数的相反数即 Java 模数 . 因此要在需求场景将正负模数统一 , 公式如下 :
a ≡ b
->( a mod m + m ) mod m = ( b mod m + m ) mod m
哈希表与前缀和结合
问题特征
哈希前缀和结合一般能够解决求满足特定条件的子数组的个数的问题 , 这类问题如果不涉及模计算 , 数据通常有负 , 无法使用滑动窗口算法解决 . sum[i] - sum[j]
表示 从下标 j + 1
到 i
的共 i - j
个元素的总和 , 通常题目条件有 :
- 是否存在 / 获取最长子数组 : 523. 连续的子数组和 和 525. 连续数组 分别对应两种情况 .
- 满足条件的子数组个数 : 560. 和为 K 的子数组 模板题 .
前缀和补足了滑动窗口的数据局限性 , 将维护变量优化成寻找两个索引值满足条件 , 哈希表在其中起到了保存前索引的功能 .
套路
- 获取前缀和数组 .
- 可以在接下来的遍历中获取前缀和 , 将 n 空间压缩成 1 空间 , 但由于前缀和数组和本数组的错位性 ( 通常 sum[0] 取 0 ) , 需要对初始化的哈希表进行预处理 .
- 推导题目要求的条件公式 .
- 分离条件公式中的前索引值和当前索引值到等式两端 .
- 在哈希表中寻找当前索引满足公式当前索引侧的个数 / 索引 .
- 将当前索引视为前索引记录满足等式另一侧的个数 / 索引 .
- 根据题目要求返回的数据不同维护不同变量 .
- 如是否存在 / 获取最长子数组问题 : 哈希表存值最早出现的位置索引 , 查最早出现位置后满足条件的位置 , 返回存在性 / 其中的最大值 .
- 满足条件的子数组个数问题 : 每次遍历使值满足条件的哈希值增加 , 维护值满足条件的子数组数量 , 查后续位置满足条件的个数 , 维护满足个数 .
就 560. 和为 K 的子数组 而言 , 根据套路步骤获得的条件公式 : sum[i] - sum[j] = k
, 其中 sum[j]
为前索引值 , 移项得 sum[j] = k - sum[i]
, 题目要求返回满足条件的子数组个数 , 所以存值等于 sum[i] - k
出现的次数 , 每次查询 sum[i]
在哈希表的映射值即可 .
看较复杂情况 2845. 统计趣味子数组的数目 题目的条件公式为 ( sum[i] - sum[j] ) % modulo = k
, 经过变换得到 ( sum[i] - k ) % modulo = sum[j] % modulo
, 所以要存 sum[i] % modulo
, 查 ( sum[i] - k ) % modulo
.
此外 , 如果题目要求返回子数组中特定值元素个数等于给定值的子数组个数 , 如 1248. 统计「优美子数组」 统计子数组中恰好有 k 个奇数数字的子数组个数时 , 可以先将对数组进行预处理 ( 如取奇数元素等于 1 , 其他等于 0 ) 后再推演套路 .