题目描述与示例
题目描述
给定一个含有N
个正整数的数组,求出有多少个连续区间(包括单个正整数),它们的和大于等于x
。
输入描述
第一行两个整数N
x
(0 < N <= 100000
,0 <= x <= 10000000
)
第二行有N
个正整数(每个正整数小于等于100
)。
输出描述
输出一个整数,表示所求的个数。
示例一
输入
3 7
3 4 7
输出
4
说明
3+4
4+7
3+4+7
7
这四组数据都是大于等于7
的,所以答案为4
示例二
输入
10 10000000
1 2 3 4 5 6 7 8 9 10
输出
0
解题思路
题目要求求连续区间的和,很容易想到使用前缀和的思路来解决。先求出前缀和数组,如对示例一求出[3, 4, 7]
的前缀和数组为pre_sum_list = [0, 3, 7, 14]
。
由于原数组中每一个元素均为非负整数,故前缀和数组一定是一个非递减数组。
对于pre_sum_list
中的每一个元素pre_sum_list[i]
,我们要找到索引j
(i < j
)。
j
是满足pre_sum_list[j] - pre_sum_list[i] >= target
的第一个索引。- 此时任意比
j
大的索引k
,都能使得pre_sum_list[k] - pre_sum_list[i] >= target
成立,即区间[i,k]
是一个符合要求的区间。 - 由于前缀和数组是一个非递减数组,
j
的寻找可以很容易地使用二分查找来完成。 - 假设
j
已经求出,那么对于i
而言,一共存在n+1-j
个这样的区间,满足连续区间和大于等于target
。将所有i
对应的n+1-j
计算并求和,即可以得到答案。
代码
Python
# 题目:【前缀和】2023B-寻找连续区间
# 分值:100
# 作者:闭着眼睛学数理化
# 算法:前缀和/二分查找
# 代码有看不懂的地方请直接在群上提问
from itertools import accumulate
n, target = map(int, input().split())
nums = list(map(int, input().split()))
# 构建前缀和数组
pre_sum_list = [0] + list(accumulate(nums))
# 初始化答案
ans = 0
# 只需要遍历前n个前缀和即可,最后一个前缀和无需考虑
for i in range(n):
# 二分查找的目标值
bi_target = pre_sum_list[i] + target
# 优化:如果发现二分目标值已经超过了数组中所有元素总和
# 则必然无法找到以i为起始位置的连续区间,其和大于target
# 故直接退出循环即可
if bi_target > pre_sum_list[-1]:
break
# 左闭右开区间
# 注意前缀和数组的长度为n+1,故此处right初始化为n+2
left, right = i, n+2
while left < right:
mid = left + (right - left) // 2
# 找到第一个大于等于bi_target的数的索引
if pre_sum_list[mid] >= bi_target:
right = mid
else:
left = mid + 1
# 退出while循环时,j = left = right
# 是使得pre_sum_list[j] >= bi_target的第一个索引
# 一共有n+1-j个区间,其区间和大于target
ans += (n+1-left)
print(ans)
Java
import java.util.Scanner;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int target = scanner.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = scanner.nextInt();
}
int[] preSum = new int[n + 1];
preSum[0] = 0;
for (int i = 0; i < n; i++) {
preSum[i + 1] = preSum[i] + nums[i];
}
int ans = 0;
for (int i = 0; i < n; i++) {
int biTarget = preSum[i] + target;
if (biTarget > preSum[n]) {
break;
}
int left = i;
int right = n + 2;
while (left < right) {
int mid = left + (right - left) / 2;
if (preSum[mid] >= biTarget) {
right = mid;
} else {
left = mid + 1;
}
}
ans += (n + 1 - left);
}
System.out.println(ans);
}
}
C++
“`C++
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n, target;
cin >> n >> target;
vector<int> nums(n);
<pre><code>for (int i = 0; i < n; i++) {
cin >> nums[i];
}
vector<int> preSum(n + 1, 0);
for (int i = 0; i < n; i++) {
preSum[i + 1] = preSum[i] + nums[i];
}
int ans = 0;
for (int i = 0; i < n; i++) {
int biTarget = preSum[i] + target;
if (biTarget > preSum[n]) {
break;
}
int left = i;
int right = n + 2;
while (left < right) {
int mid = left + (right – left) / 2;
if (preSum[mid] >= biTarget) {
right = mid;
} else {
left = mid + 1;
}
}
ans += (n + 1 – left);
}
cout << ans << endl;
return 0;
</code></pre>
}
“`
时空复杂度
时间复杂度:O(NlogN)
。构建前缀和的时间复杂度为O(N)
。对于前缀和数组中的每一个元素pre_sum_list[i]
,都要进行二分查找,单次二分查找的时间复杂度为O(logN)
,一共需要进行N
次二分查找。
空间复杂度:O(N)
。前缀和数组所占空间。
说明
华为OD机试有三道题⽬,第⼀道和第⼆道属于简单或中等题,分值为 100 分,第三道为中等或困难题,分值为 200分,总分为 400 分。
机试分数越⾼评级越⾼,⼯资也就越⾼。
关于华为 OD 机试更加详细的介绍可以查看这篇⽂章:华为OD机考须知
关于机考题目汇总可以看这篇文章:华为OD机试真题 2023 A+B+C+D卷 + 2024新卷(Python&Java&C++)⽬录汇总(每⽇更新)