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

微众银行笔试/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

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

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 ii<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} ji]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×iv×presum[i]=u×jv×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×iv×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);
    }
}

你可能感兴趣的:(算法总结,算法)