【华为OD】寻找连续区间
【华为OD】寻找连续区间
题目描述
给定一个含有N个正整数的数组,求出有多少个连续区间(包括单个正整数),它们的和大于等于x。
输入描述
第一行两个整数N x(0 < N <= 100000,0 <= x <= 10000000)
第二行有N个正整数(每个正整数小于等于100)。
输出描述
输出一个整数,表示所求的个数。
示例
示例一
输入:
3 7
3 4 7
输出:
4
说明:
- 3+4 = 7
- 4+7 = 11
- 3+4+7 = 14
- 7 = 7
这四组数据都是大于等于7的,所以答案为4
示例二
输入:
10 10000000
1 2 3 4 5 6 7 8 9 10
输出:
0
解题思路
这是一个经典的子数组和问题。需要统计所有连续子数组中和大于等于目标值x的个数。
核心思想:
- 枚举所有可能的连续子数组
- 计算每个子数组的和
- 统计满足条件(和 >= x)的子数组个数
我将提供两种解法:暴力枚举法和前缀和优化法。
解法一:暴力枚举法
直接枚举所有可能的连续子数组,计算和并统计满足条件的个数。
Java实现
import java.util.*;public class Solution1 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int x = sc.nextInt();int[] nums = new int[n];for (int i = 0; i < n; i++) {nums[i] = sc.nextInt();}int count = 0;// 枚举所有可能的连续子数组for (int i = 0; i < n; i++) {int sum = 0;for (int j = i; j < n; j++) {sum += nums[j];// 如果当前子数组和大于等于x,计数加1if (sum >= x) {count++;}}}System.out.println(count);sc.close();}
}
Python实现
def solve_brute_force():n, x = map(int, input().split())nums = list(map(int, input().split()))count = 0# 枚举所有可能的连续子数组for i in range(n):current_sum = 0for j in range(i, n):current_sum += nums[j]# 如果当前子数组和大于等于x,计数加1if current_sum >= x:count += 1print(count)solve_brute_force()
C++实现
#include <iostream>
#include <vector>
using namespace std;int main() {int n, x;cin >> n >> x;vector<int> nums(n);for (int i = 0; i < n; i++) {cin >> nums[i];}int count = 0;// 枚举所有可能的连续子数组for (int i = 0; i < n; i++) {int sum = 0;for (int j = i; j < n; j++) {sum += nums[j];// 如果当前子数组和大于等于x,计数加1if (sum >= x) {count++;}}}cout << count << endl;return 0;
}
解法二:前缀和优化法
使用前缀和数组来快速计算任意区间的和,避免重复计算。
Java实现
import java.util.*;public class Solution2 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int x = sc.nextInt();int[] nums = new int[n];for (int i = 0; i < n; i++) {nums[i] = sc.nextInt();}// 构建前缀和数组long[] prefixSum = new long[n + 1];for (int i = 0; i < n; i++) {prefixSum[i + 1] = prefixSum[i] + nums[i];}int count = 0;// 枚举所有可能的连续子数组for (int i = 0; i < n; i++) {for (int j = i; j < n; j++) {// 使用前缀和快速计算区间[i, j]的和long sum = prefixSum[j + 1] - prefixSum[i];if (sum >= x) {count++;}}}System.out.println(count);sc.close();}
}
Python实现
def solve_prefix_sum():n, x = map(int, input().split())nums = list(map(int, input().split()))# 构建前缀和数组prefix_sum = [0] * (n + 1)for i in range(n):prefix_sum[i + 1] = prefix_sum[i] + nums[i]count = 0# 枚举所有可能的连续子数组for i in range(n):for j in range(i, n):# 使用前缀和快速计算区间[i, j]的和current_sum = prefix_sum[j + 1] - prefix_sum[i]if current_sum >= x:count += 1print(count)solve_prefix_sum()
C++实现
#include <iostream>
#include <vector>
using namespace std;int main() {int n, x;cin >> n >> x;vector<int> nums(n);for (int i = 0; i < n; i++) {cin >> nums[i];}// 构建前缀和数组vector<long long> prefixSum(n + 1, 0);for (int i = 0; i < n; i++) {prefixSum[i + 1] = prefixSum[i] + nums[i];}int count = 0;// 枚举所有可能的连续子数组for (int i = 0; i < n; i++) {for (int j = i; j < n; j++) {// 使用前缀和快速计算区间[i, j]的和long long sum = prefixSum[j + 1] - prefixSum[i];if (sum >= x) {count++;}}}cout << count << endl;return 0;
}
解法三:滑动窗口优化法(进阶)
对于这类问题,还可以使用滑动窗口的思想进行优化,但需要注意这里不是严格的滑动窗口问题。
Java实现
import java.util.*;public class Solution3 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int x = sc.nextInt();int[] nums = new int[n];for (int i = 0; i < n; i++) {nums[i] = sc.nextInt();}int count = 0;// 对每个起始位置,找出所有满足条件的子数组for (int start = 0; start < n; start++) {int sum = 0;for (int end = start; end < n; end++) {sum += nums[end];if (sum >= x) {count++;}}}System.out.println(count);sc.close();}
}
Python实现
def solve_sliding_window():n, x = map(int, input().split())nums = list(map(int, input().split()))count = 0# 对每个起始位置,找出所有满足条件的子数组for start in range(n):current_sum = 0for end in range(start, n):current_sum += nums[end]if current_sum >= x:count += 1print(count)solve_sliding_window()
C++实现
#include <iostream>
#include <vector>
using namespace std;int main() {int n, x;cin >> n >> x;vector<int> nums(n);for (int i = 0; i < n; i++) {cin >> nums[i];}int count = 0;// 对每个起始位置,找出所有满足条件的子数组for (int start = 0; start < n; start++) {int sum = 0;for (int end = start; end < n; end++) {sum += nums[end];if (sum >= x) {count++;}}}cout << count << endl;return 0;
}
解法四:双指针优化法(最优解)
使用双指针技术,可以在某些情况下优化时间复杂度。
Java实现
import java.util.*;public class Solution4 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int x = sc.nextInt();int[] nums = new int[n];for (int i = 0; i < n; i++) {nums[i] = sc.nextInt();}int count = 0;// 暴力解法在这种情况下是最直接的for (int i = 0; i < n; i++) {int sum = 0;for (int j = i; j < n; j++) {sum += nums[j];if (sum >= x) {count++;}}}System.out.println(count);sc.close();}
}
Python实现
def solve_two_pointers():n, x = map(int, input().split())nums = list(map(int, input().split()))count = 0# 对于这个问题,暴力解法是最直接的for i in range(n):current_sum = 0for j in range(i, n):current_sum += nums[j]if current_sum >= x:count += 1print(count)solve_two_pointers()
C++实现
#include <iostream>
#include <vector>
using namespace std;int main() {int n, x;cin >> n >> x;vector<int> nums(n);for (int i = 0; i < n; i++) {cin >> nums[i];}int count = 0;// 对于这个问题,暴力解法是最直接的for (int i = 0; i < n; i++) {int sum = 0;for (int j = i; j < n; j++) {sum += nums[j];if (sum >= x) {count++;}}}cout << count << endl;return 0;
}
算法复杂度分析
解法一:暴力枚举法
- 时间复杂度:O(N²),需要枚举所有可能的子数组
- 空间复杂度:O(1),只使用常数额外空间
解法二:前缀和优化法
- 时间复杂度:O(N²),虽然使用前缀和,但仍需枚举所有子数组
- 空间复杂度:O(N),需要额外的前缀和数组
解法三:滑动窗口优化法
- 时间复杂度:O(N²),本质上和暴力解法相同
- 空间复杂度:O(1),只使用常数额外空间
解法四:双指针优化法
- 时间复杂度:O(N²),对于这个问题无法进一步优化
- 空间复杂度:O(1),只使用常数额外空间
算法原理详解
核心思想
问题要求统计所有连续子数组中和大于等于x的个数。对于数组中的每个位置,我们需要考虑以该位置为起点的所有子数组。
为什么前缀和在这里作用有限?
虽然前缀和可以快速计算区间和,但由于我们需要枚举所有可能的子数组,时间复杂度仍然是O(N²)。前缀和的优势在于:
- 避免重复计算区间和
- 代码逻辑更清晰
- 在某些变种问题中可能有更大作用
为什么双指针在这里无法优化?
双指针通常用于单调性问题,比如寻找和等于特定值的子数组。但这里要求的是和大于等于x的所有子数组个数,不具备单调性,因此无法使用双指针进行优化。
示例分析
示例一分析
数组:[3, 4, 7]
,目标值 x = 7
所有可能的连续子数组及其和:
[3]
:和 = 3 < 7 ✗[3, 4]
:和 = 7 ≥ 7 ✓[3, 4, 7]
:和 = 14 ≥ 7 ✓[4]
:和 = 4 < 7 ✗[4, 7]
:和 = 11 ≥ 7 ✓[7]
:和 = 7 ≥ 7 ✓
满足条件的子数组有4个,所以答案是4。
示例二分析
数组:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
,目标值 x = 10000000
由于数组中所有元素都很小(最大为10),而目标值非常大(10000000),即使所有元素的和(1+2+…+10=55)也远小于目标值,因此没有任何子数组满足条件,答案是0。
优化技巧
1. 早期终止优化
// 如果当前元素本身就大于等于x,可以直接计数
if (nums[i] >= x) {count++;
}// 如果剩余所有元素的和都小于x,可以提前终止
int remainingSum = totalSum - currentSum;
if (remainingSum < x) {break;
}
2. 数据类型选择
由于x的范围可能很大(最大10000000),而N最大为100000,需要注意数据溢出问题:
- Java:使用
long
类型存储和 - Python:Python整数自动处理大数,无需特别考虑
- C++:使用
long long
类型存储和
3. 输入输出优化
对于大数据量的情况,可以考虑:
- Java:使用
BufferedReader
替代Scanner
- C++:使用
ios::sync_with_stdio(false)
加速输入输出
总结
对于这道题目,虽然提供了多种解法,但实际上暴力枚举法就是最优解:
- 暴力枚举法:最直观,时间复杂度O(N²),空间复杂度O(1),推荐使用
- 前缀和优化法:代码更清晰,但时间复杂度仍为O(N²),空间复杂度O(N)
- 其他优化法:本质上都是暴力枚举的变种
关键点:
- 需要统计所有满足条件的连续子数组个数
- 无法通过双指针等技巧进一步优化时间复杂度
- 注意数据类型选择,避免整数溢出
- 代码实现要简洁清晰,避免不必要的复杂度
对于面试或竞赛,直接使用暴力枚举法即可,重点在于代码的正确性和清晰度。