微众银行笔试/20230903/第三题赏析
在介绍这题前,先介绍lc 560 和为K的子数组
一、和为K的子数组
链接:https://leetcode.cn/problems/subarray-sum-equals-k/description/
给你一个整数数组 nums
和一个整数 k
,请你统计并返回该数组中和为 k
的连续子数组的个数。
(1)暴力解法
回溯法遍历每一个连续子数组,计算子数组的和并与K相比。时间复杂度 O ( N 3 ) O(N^3) O(N3) 。
这样做显然不行,有优化的地方吗?有。计算子数组的和。
(2)前缀和优化
我们知道前缀和这一技巧,在做一次预处理后,计算某个区间的和只需要O(1)时间。那么题目转化为:求有多少个区间【l,r】和为K,其中l和r范围是0~N-1,且l<r。
具体的解题过程就变成了两层循环:
for(int r=0; r<N; ++r){
for(int l=0; l<=r; ++l){
//求l,r区间和
//判断区间和是否与k相等
}
}
能不能再优化?
(3)哈希表优化
计算公式: p r e s u m [ j ] − p r e s u m [ i ] = k presum[j] - presum[i] = k presum[j]−presum[i]=k ,那么 p r e s u m [ j ] − k = p r e s u m [ i ] presum[j] -k = presum[i] presum[j]−k=presum[i]
转换思路:考虑以 j j j为右端点的区间,其区间和为K的个数。这等价于求多少个 i < j i<j i<j,满足 $presum[i] = presum[j] -k $
等等,这个公式有点熟悉?力扣梦开始的地方,力扣第一题,两数之和。这里是两数之差。
接下来的思路就很明朗了,空间换时间,利用哈希宝提前存每个presum[i]。
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的实现。
二、微众银行第三题-平均值
2.1 题目详情
(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)样例
输入
6 5 2
2 4 1 3 2 3
输出
6
2.2 思路分析
仔细思考,如果将题目描述的中平均值去除,这不就是和为K的子数组吗?
条件给的数组平均值与一个真分数,是想要我们干什么呢?回到判断相等的公式。
p r e s u m [ j ] − p r e s u m [ i ] j − i ] = u v \frac{presum[j]-presum[i]}{j-i]} = \frac{u}{v} j−i]presum[j]−presum[i]=vu
我们将它变形一下:
u × i − v × p r e s u m [ i ] = u × j − v × p r e s u m [ j ] u\times i - v \times presum[i] = u \times j - v \times presum[j] u×i−v×presum[i]=u×j−v×presum[j]
等等,这两边的形式好像高度对称啊,我们定义一个新数组 a [ i ] = u × i − v × p r e s u m [ i ] a[i] = u \times i - v \times presum[i] a[i]=u×i−v×presum[i],上面公式转化为了
a [ i ] = a [ j ] − 0 a[i] = a[j] - 0 a[i]=a[j]−0
这不就是两数之差为k嘛,只不过k为0。兜兜转转,我们又回到了力扣第一题。
2.3 题解
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);
}
}