Files
leetcode-master/problems/kamacoder/0058.区间和.md
youngyangyang04 7aa973b561 Update
2025-07-06 11:28:34 +08:00

411 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 58. 区间和
> 本题为代码随想录后续扩充题目还没有视频讲解顺便让大家练习一下ACM输入输出模式笔试面试必备
[题目链接](https://kamacoder.com/problempage.php?pid=1070)
题目描述
给定一个整数数组 Array请计算该数组在每个指定区间内元素的总和。
输入描述
第一行输入为整数数组 Array 的长度 n接下来 n 行,每行一个整数,表示数组的元素。随后的输入为需要计算总和的区间,直至文件结束。
输出描述
输出每个指定区间内元素的总和。
输入示例
```
5
1
2
3
4
5
0 1
1 3
```
输出示例
```
3
9
```
数据范围:
0 < n <= 100000
## 思路
本题我们来讲解 数组 上常用的解题技巧前缀和
首先来看本题我们最直观的想法是什么
那就是给一个区间然后 把这个区间的和都累加一遍不就得了是一道简单不能再简单的题目
代码如下
```CPP
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, a, b;
cin >> n;
vector<int> vec(n);
for (int i = 0; i < n; i++) cin >> vec[i];
while (cin >> a >> b) {
int sum = 0;
// 累加区间 a 到 b 的和
for (int i = a; i <= b; i++) sum += vec[i];
cout << sum << endl;
}
}
```
代码一提交,发现超时了.....
我在制作本题的时候,特别制作了大数据量查询,卡的就是这种暴力解法。
来举一个极端的例子如果我查询m次每次查询的范围都是从0 到 n - 1
那么该算法的时间复杂度是 O(n * m) m 是查询的次数
如果查询次数非常大的话,这个时间复杂度也是非常大的。
接下来我们来引入前缀和,看看前缀和如何解决这个问题。
前缀和的思想是重复利用计算过的子数组之和,从而降低区间查询需要累加计算的次数。
**前缀和 在涉及计算区间和的问题时非常有用**
前缀和的思路其实很简单,我给大家举个例子很容易就懂了。
例如,我们要统计 vec[i] 这个数组上的区间和。
我们先做累加,即 p[i] 表示 下标 0 到 i 的 vec[i] 累加 之和。
如图:
![](https://file1.kamacoder.com/i/algo/20240627110604.png)
如果我们想统计在vec数组上 下标 2 到下标 5 之间的累加和,那是不是就用 p[5] - p[1] 就可以了。
为什么呢?
`p[1] = vec[0] + vec[1];`
`p[5] = vec[0] + vec[1] + vec[2] + vec[3] + vec[4] + vec[5];`
`p[5] - p[1] = vec[2] + vec[3] + vec[4] + vec[5];`
这不就是我们要求的 下标 2 到下标 5 之间的累加和吗。
如图所示:
![](https://file1.kamacoder.com/i/algo/20240627111319.png)
`p[5] - p[1]` 就是 红色部分的区间和。
而 p 数组是我们之前就计算好的累加和,所以后面每次求区间和的之后 我们只需要 O(1) 的操作。
**特别注意** 在使用前缀和求解的时候,要特别注意 求解区间。
如上图,如果我们要求 区间下标 [2, 5] 的区间和,那么应该是 p[5] - p[1],而不是 p[5] - p[2]。
**很多录友在使用前缀和的时候,分不清前缀和的区间,建议画一画图,模拟一下 思路会更清晰**。
本题C++代码如下:
```CPP
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, a, b;
cin >> n;
vector<int> vec(n);
vector<int> p(n);
int presum = 0;
for (int i = 0; i < n; i++) {
cin >> vec[i];
presum += vec[i];
p[i] = presum;
}
while (cin >> a >> b) {
int sum;
if (a == 0) sum = p[b];
else sum = p[b] - p[a - 1];
cout << sum << endl;
}
}
```
C++ 代码 面对大量数据 读取 输出操作最好用scanf 和 printf耗时会小很多
```CPP
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n, a, b;
cin >> n;
vector<int> vec(n);
vector<int> p(n);
int presum = 0;
for (int i = 0; i < n; i++) {
scanf("%d", &vec[i]);
presum += vec[i];
p[i] = presum;
}
while (~scanf("%d%d", &a, &b)) {
int sum;
if (a == 0) sum = p[b];
else sum = p[b] - p[a - 1];
printf("%d\n", sum);
}
}
```
## 其他语言版本
### Java
```Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int[] vec = new int[n];
int[] p = new int[n];
int presum = 0;
for (int i = 0; i < n; i++) {
vec[i] = scanner.nextInt();
presum += vec[i];
p[i] = presum;
}
while (scanner.hasNextInt()) {
int a = scanner.nextInt();
int b = scanner.nextInt();
int sum;
if (a == 0) {
sum = p[b];
} else {
sum = p[b] - p[a - 1];
}
System.out.println(sum);
}
scanner.close();
}
}
```
### Python
```python
import sys
input = sys.stdin.read
def main():
data = input().split()
index = 0
n = int(data[index])
index += 1
vec = []
for i in range(n):
vec.append(int(data[index + i]))
index += n
p = [0] * n
presum = 0
for i in range(n):
presum += vec[i]
p[i] = presum
results = []
while index < len(data):
a = int(data[index])
b = int(data[index + 1])
index += 2
if a == 0:
sum_value = p[b]
else:
sum_value = p[b] - p[a - 1]
results.append(sum_value)
for result in results:
print(result)
if __name__ == "__main__":
main()
```
### JavaScript
``` JavaScript
function prefixSum() {
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let inputLines = [];
rl.on('line', (line) => {
inputLines.push(line.trim());
});
rl.on('close', () => {
// 读取项数 n
const n = parseInt(inputLines[0]);
// 使用前缀和,复杂度控制在 O(1)
let sum = new Array(n);
sum[0] = parseInt(inputLines[1]);
// 计算前缀和数组
for (let i = 1; i < n; i++) {
let value = parseInt(inputLines[i + 1]);
sum[i] = sum[i - 1] + value;
}
// 处理区间和查询
for (let i = n + 1; i < inputLines.length; i++) {
let [left, right] = inputLines[i].split(' ').map(Number);
if (left === 0) {
console.log(sum[right]);
} else {
console.log(sum[right] - sum[left - 1]);
}
}
});
}
```
### C
```C
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int num;
// 读取数组长度
scanf("%d", &num);
// 使用动态内存分配而不是静态数组,以适应不同的输入大小
int *a = (int *)malloc((num + 1) * sizeof(int));
// 初始化前缀和数组的第一个元素为0
a[0] = 0;
// 读取数组元素并计算前缀和
for (int i = 1; i <= num; i++)
{
int mm;
scanf("%d", &mm);
// 累加前缀和
a[i] = a[i - 1] + mm;
}
int m, n;
// 循环读取区间并计算区间和,直到输入结束
// scanf()返回成功匹配和赋值的个数,到达文件末尾则返回 EOF
while (scanf("%d%d", &m, &n) == 2)
{
// 输出区间和注意区间是左闭右开因此a[n+1]是包含n的元素的前缀和
printf("%d\n", a[n+1] - a[m]);
}
// 释放之前分配的内存
free(a);
return 0;
}
```
### Go
```go
package main
import (
"fmt"
"bufio"
"strconv"
"os"
)
func main() {
// bufio中读取数据的接口因为数据卡的比较严导致使用fmt.Scan会超时
scanner := bufio.NewScanner(os.Stdin)
// 获取数组大小
scanner.Scan()
n, _ := strconv.Atoi(scanner.Text())
// 获取数组元素的同时计算前缀和,一般建议切片开大一点防止各种越界问题
arr := make([]int, n + 1)
for i := 0; i < n; i++ {
scanner.Scan()
arr[i], _ = strconv.Atoi(scanner.Text())
if i != 0 {
arr[i] += arr[i - 1]
}
}
/*
区间[l, r]的和可以使用区间[0, r]和[0, l - 1]相减得到,
在代码中即为arr[r]-arr[l-1]。这里需要注意l-1是否越界
*/
for {
var l, r int
scanner.Scan()
_, err := fmt.Sscanf(scanner.Text(), "%d %d", &l, &r)
if err != nil {
return
}
if l > 0 {
fmt.Println(arr[r] - arr[l - 1])
} else {
fmt.Println(arr[r])
}
}
}
```