均值为u/v的子数组

在介绍这题前,先介绍lc 560 和为K的子数组。

和为K的连续子数组

链接:https://leetcode.cn/problems/subarray-sum-equals-k/description/

给你一个整数数组 nums 和一个整数 k ,请你统计并返回该数组中和为 k 的连续子数组的个数。

暴力解法

回溯法遍历每一个连续子数组,计算子数组的和并与K相比。时间复杂度$O(N^3)$ 。

这样做显然不行,有优化的地方吗?有。计算子数组的和。

前缀和优化

我们知道前缀和这一技巧,在做一次预处理后,计算某个区间的和只需要O(1)时间。那么题目转化为:求有多少个区间【l,r】和为K,其中l和r范围是0~N-1,且l<r。

具体的解题过程就变成了两层循环:

1
2
3
4
5
6
for(int r=0; r<N; ++r){
for(int l=0; l<=r; ++l){
//求l,r区间和
//判断区间和是否与k相等
}
}

能不能再优化?

哈希表优化

计算公式:$presum[j] - presum[i] = k$ ,那么 $presum[j] -k = presum[i]$

转换思路:考虑以$j$为右端点的区间,其区间和为K的个数。这等价于求多少个$i<j$,满足 $presum[i] = presum[j] -k $

等等,这个公式有点熟悉?力扣梦开始的地方,力扣第一题,两数之和。这里是两数之差。

接下来的思路就很明朗了,空间换时间,利用哈希宝提前存每个presum[i]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int subarraySum(int[] nums, int k) {
int N = nums.length;
int[] preSum = new int[N];
preSum[0] = nums[0];
for(int i=1; i<N; ++i){
preSum[i] = preSum[i-1]+nums[i];
}
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1); // 很重要,对应preSum[-1]=0
int ans = 0;
for(int j=0; j<N; ++j){
if(map.containsKey(preSum[j]-k)) {
ans += map.get(preSum[j]-k);
}
map.put(preSum[j], map.getOrDefault(preSum[j], 0)+1);
}
return ans;
}
}

细节注意:

  • 以往前缀和下标都是不对应的,比如preSum[i+1] 为从0到i得区间和。

这道题的下标是对应的,原因是 map.put(0, 1);

  • map.put(0, 1); 的作用

其实还是前缀和下标对应的问题。当我们要计算第一个区间,即[0,0]的区间和时,我们采用了preSum[0]-0的实现。

微众银行第三题-平均值

微众银行笔试/20230903/第三题赏析

题目详情

(1)题目描述

小明有一个数组。他挑选了一个有理数u/v,现在他想知道这个数组有多少个子区间的平均值恰好等于u/v。数组的子区间即是数组中连续的一段区间,如数组[4,2,6]有6个子区间 [4],[2],[6],[4,2],[2,6],[4,2,6]。

(2)输入描述

第一行有三个整数 n,u,v (1 ≤ n, v ≤ 100000,1 ≤ u ≤ n *v),代表数组的长度,小明选择的有理数的分子和分母。

输入保证u和v的最大公因数是1,即u/v是最简分数。

第二行有n个绝对值不超过1000000的整数,代表数组中的元素。

数字间两两有空格隔开。

(3)输出描述

输出一个非负整数,代表所求的答案。

(4)样例

输入

1
2
6 5 2
2 4 1 3 2 3

输出

1
6

思路分析

仔细思考,如果将题目描述的中平均值去除,这不就是和为K的子数组吗?

条件给的数组平均值与一个真分数,是想要我们干什么呢?回到判断相等的公式。

$\frac{presum[j]-presum[i]}{j-i]} = \frac{u}{v}$

我们将它变形一下:

$u\times i - v \times presum[i] = u \times j - v \times presum[j]$

等等,这两边的形式好像高度对称啊,我们定义一个新数组 $a[i] = u \times i - v \times presum[i]$,上面公式转化为了

$a[i] = a[j] - 0$

这不就是两数之差为k嘛,只不过k为0。兜兜转转,我们又回到了力扣第一题。

题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(), u = scanner.nextInt(), v = scanner.nextInt();
int[] a = new int[n];
int[] psum = new int[n + 1];

for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
psum[i + 1] = psum[i] + a[i];
}

long rs = 0;
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
if (1L * (psum[j] - psum[i - 1]) * v == 1L * u * (j - i + 1)) {
rs++;
}
}
}

System.out.println(rs);
}
}
作者

Desirer

发布于

2024-06-05

更新于

2024-06-09

许可协议