Skip to content

Commit

Permalink
974
Browse files Browse the repository at this point in the history
  • Loading branch information
yk committed Feb 20, 2024
1 parent 35e0914 commit 6441364
Showing 1 changed file with 42 additions and 49 deletions.
91 changes: 42 additions & 49 deletions content/posts/algorithm/data structure/数组-前缀和数组.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
---
title: '数组-前缀和数组'
date: 2023-02-19T15:57:07+08:00
lastmod: 2024-02-20
tags: [Array]
series: [data structure, trick]
categories: [algorithm]
---

### 概念

应用场景:在原始数组不会被修改的情况下,**快速、频繁查询某个区间的累加和**
应用场景:在原始数组不会被修改的情况下,**快速、频繁查询某个区间的累加和**通常涉及到连续子数组和相关问题时,就可以考虑使用前缀和技巧了。

核心思路:开辟新数组 `preSum[i]` 来存储原数组 `nums[0..i-1] `的累加和,`preSum[0] = 0`。这样,当求原数组区间和就比较容易了,区间 `[i..j]` 的和等于 `preSum[j+1] - preSum[i]` 的结果值。

```JavaScript
const preSum = [0]
preSum[i] = preSum[i - 1] + nums[i - 1]
// 查询
preSum[r + 1] - preSum[l]

// 或者
const preSum = [nums[0]]
preSum[i] = preSum[i-1] + nums[i]
// 查询
preSum[r] - preSum[l-1] // 需要判断 l 为 0 的情况,直接返回 preSum[r],这种的劣势是需要判断边界条件
preSum[j + 1] - preSum[i]
```

### 构造:[lc.303 区域和检索-数组不可变](https://leetcode.cn/problems/range-sum-query-immutable/)
Expand Down Expand Up @@ -54,38 +49,6 @@ NumArray.prototype.sumRange = function(left, right) {
*/
```

---

上方那种事官解给出的逻辑,下面这种可能更容易理解一些。

```js
/**
* @param {number[]} nums
*/
var NumArray = function (nums) {
this.preSums = [nums[0]]
for (let i = 1; i < nums.length; ++i) {
this.preSums[i] = this.preSums[i - 1] + nums[i]
}
}

/**
* @param {number} left
* @param {number} right
* @return {number}
*/
NumArray.prototype.sumRange = function (left, right) {
if (left === 0) return this.preSums[right]
return this.preSums[right] - this.preSums[left - 1]
}

/**
* Your NumArray object will be instantiated and called as such:
* var obj = new NumArray(nums)
* var param_1 = obj.sumRange(left,right)
*/
```

### 构造:[lc.304 二维区域和检索-矩阵不可变](https://leetcode.cn/problems/range-sum-query-2d-immutable/)

```js
Expand Down Expand Up @@ -130,9 +93,11 @@ NumMatrix.prototype.sumRegion = function (row1, col1, row2, col2) {

### lc.523 连续的子数组和

这道题有点意思的,首先要知道一个数学知识:**同余定理:(a - b) % k == 0,则 a % k == b % k**
这道题有点意思的,首先要知道一个数学知识:**同余定理:a, b 模 k 后的余数相同,则 a,b 对 k 同余**,本题需要利用同余的整除性性质:

** `a % k == b % k`,则 `(a-b) % k === 0`。即同余数之差能被 k 整除 **

根据题意,需要获取到的信息是:**(preSum[j] - preSum[i]) % k === 0**,如过存在则返回 true,那么就可以转化为:**寻找是否有两个前缀和能 % k 后,余数相同的问题了~**,那就很自然想到用哈希表来存储 `{余数:索引}`了,不然这题还真挺难想的~ 再一次 respect 数学!
根据题意,需要获取到的信息是:**(preSum[j] - preSum[i]) % k === 0**,如过存在则返回 true,那么就可以转化为:**寻找是否有两个前缀和能 % k 后,余数相同的问题了~**,那就很自然想到用哈希表来存储 `{余数:索引}` 了,不然这题还真挺难想的~ 再一次 respect 数学!

```js
var checkSubarraySum = function (nums, k) {
Expand All @@ -141,14 +106,14 @@ var checkSubarraySum = function (nums, k) {
let preSum = 0
for (let i = 0; i < nums.length; ++i) {
preSum += nums[i]
let b = preSum % k
if (map[b] >= -1) {
let remainder = preSum % k
if (map[remainder] >= -1) {
// 左开右闭区
if (i - map[b] >= 2) {
if (i - map[remainder] >= 2) {
return true
}
} else {
map[b] = i
map[remainder] = i
}
}
return false
Expand Down Expand Up @@ -268,8 +233,8 @@ Solution.prototype.pickIndex = function() {
*/
var subarraySum = function(nums, k) {
const preSum = [0]
for(let i = 1; i <= nums.length; ++i) {
preSum[i] = preSum[i-1] + nums[i-1]
for(let i = 0; i < nums.length; ++i) {
preSum[i + 1] = preSum[i] + nums[i]
}
let count = 0
// 遍历出所有区间
Expand Down Expand Up @@ -404,12 +369,36 @@ var maxSubarraySumCircular = function (nums) {

### lc.974 和可被 k 整除的子数组

lc.523 相呼应,如果不知道同余定理,则比较棘手。
这题与题目 「lc.560 和为 K 的子数组」非常相似,同时与 lc.523 相呼应,如果不知道同余定理,则比较棘手。

```js
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var subarraysDivByK = function (nums, k) {
let res = 0
let remainder = 0
const map = { 0: 1 } // 存储 { %k : count } 这里求数量,则初始化为 1,之前有题是求距离,初始化为了 -1
for (let i = 0; i < nums.length; ++i) {
// 当有负数时,js 语言的取模和数学上的取模是不一样的,所以为了修正这种逻辑,先 +个 k 再去模即可
remainder = (((remainder + nums[i]) % k) + k) % k
// if(remainder < 0) remainder += k // 评论里看到也可以这样修正

if (map[remainder]) {
res += map[remainder]
map[remainder]++
} else {
map[remainder] = 1
}
}
return res
}
```

`remainder = (((remainder + nums[i]) % k) + k) % k`,这里我的理解是,因为使用了数学上的同余定理性质,所以程序的取余远算应当与之保持一致,比如 js 中 `-5 % 3 == -2`,数学中 `-5 % 3 == 1`

---

### 前缀积与后缀积:[lc.238 除以自身以外的数组的乘积](https://leetcode.cn/problems/product-of-array-except-self/)
Expand Down Expand Up @@ -462,3 +451,7 @@ var productExceptSelf = function (nums) {
```

> lc.327 lc.862 两道 hard 题,后续有时间再看看 😁
### 同余定理

> [维基百科 - 同余](https://zh.wikipedia.org/wiki/%E5%90%8C%E9%A4%98)

0 comments on commit 6441364

Please sign in to comment.