问题描述
小U有一个长度为n的整数数组,并且选择了一个有理数 u/v。他现在想知道这个数组中有多少个连续的子区间,其平均值恰好等于 u/v。数组的子区间是指数组中一段连续的元素。
例如,给定数组 [2, 4, 1, 3, 2, 3] 和有理数 5/2,我们需要找出所有平均值等于 5/2 的子区间。
为了解决这个问题,我们需要找到数组中的所有连续子区间,使得这些子区间的平均值等于给定的有理数 uv\frac{u}{v}vu。让我们从分析问题开始,然后逐步构建解决方案。
问题分析
给定一个数组arr 和一个有理数 ,我们要找到数组的子区间(即连续子数组),使得这些子区间的平均值恰好等于。
假设一个子区间从索引 i 到 j(其中 ),它的平均值公式为:
其中子区间长度为 j - i + 1 ,而子区间和可以表示为。
我们要满足条件:
即:
思路
为了提高效率,我们不想直接求解所有子区间的和并逐个检查。可以通过前缀和与哈希表记录和频率的方式来加速计算。
假设:
- prefixSum[k] 表示数组前 ( k ) 个元素的和。
- 为了计算任意子区间的和,我们可以使用前缀和的差值:。
因此,我们的公式可以转化为:
整理得到:
这表明我们可以通过记录 的值来快速匹配可能的子区间。
算法步骤
-
初始化哈希表
prefixCount,用于存储每个前缀和的出现次数。初始值prefixCount[0] = 1。 -
遍历数组
arr:-
计算每个元素调整后的值
num * v - u并更新前缀和prefixSum。 -
检查当前前缀和是否在
prefixCount中:- 如果存在,则将对应的出现次数加入计数
count。
- 如果存在,则将对应的出现次数加入计数
-
更新哈希表中当前前缀和的出现次数。
-
-
返回
count,即满足条件的子区间数量。
代码实现 (Java)
以下是上述逻辑的 Java 实现:
import java.util.*;
public class Main {
public static int solution(int n, int u, int v, int[] arr) {
// 用于存储前缀和与其出现次数
HashMap<Integer, Integer> prefixCount = new HashMap<>();
prefixCount.put(0, 1); // 初始化前缀和为0的次数为1
int prefixSum = 0;
int count = 0;
for (int num : arr) {
// 更新前缀和
prefixSum += (num * v - u);
// 检查前缀和是否已存在
if (prefixCount.containsKey(prefixSum)) {
count += prefixCount.get(prefixSum); // 增加符合条件的子区间数量
}
// 更新前缀和的出现次数
prefixCount.put(prefixSum, prefixCount.getOrDefault(prefixSum, 0) + 1);
}
return count;
}
public static void main(String[] args) {
System.out.println(solution(6, 5, 2, new int[]{2, 4, 1, 3, 2, 3}) == 6);
System.out.println(solution(5, 1, 1, new int[]{1, 1, 1, 1, 1}) == 15);
System.out.println(solution(4, 2, 1, new int[]{2, 2, 2, 2}) == 10);
}
}
时间复杂度
该算法的时间复杂度为 O(n),其中 n 是数组的长度,因为只需遍历数组一次,并且哈希表的插入与查找操作平均为 O(1)。
总结
通过前缀和和哈希表来求解连续子区间的和问题,巧妙地将条件从平均值转化为差值,这种思路可以高效地解决问题。