字节跳动2025年校招笔试手撕真题教程(二)
一、题目
题目内容
小基有一个数组,他想知道有多少连续子数组的和同时是 3 和 5 的倍数,但不是 4 的倍数。
输入描述
第一行输入一个整数 n(1≤n≤105)n(1\le n\le 10^5)n(1≤n≤105) 表示数组长度。
第二行输入 nnn 个整数表示数组 ai(1≤ai≤109)a_i(1\le a_i\le 10^9)ai(1≤ai≤109)。
输出描述
一个整数。
样例1
输入:
1 2 |
|
输出:
1 |
|
二、分析
这个问题其实是要计算符合条件的连续子数组的数量。这个条件是比较严格的,既要满足是3和5的倍数,又不能是4的倍数,所以需要仔细地分析和对比。
首先,我得明确一下问题的基本概念。连续子数组就是原数组中连续元素组成的一个子序列。比如原数组是 [1,2,3],那么它的连续子数组包括 [1], [2], [3], [1,2], [2,3], [1,2,3] 这些。这些子数组的和分别是1,2,3,3,5,6。现在我们要找的是其中和满足同时是3和5的倍数,但又不是4的倍数的那些子数组。那么如何高效地计算这些子数组呢?这时候我想到了前缀和这个概念。前缀和数组S[i] 表示的是从开头到第i个元素的和。这样,子数组从j到i的和就可以表示为 S[i] - S[j]。这个转换很重要,因为它把求子数组和的问题转化成了求两个前缀和之差的问题。
接下来,我考虑如何通过模运算来判断条件。因为我们要找的是和同时是3和5的倍数,但不是4的倍数,所以可以先分别计算前缀和对3、5和4取模后的结果。假设子数组的和是H,那么H %3 ==0,H%5==0,同时H%4 !=0。但直接计算每个子数组的和再判断的话,时间复杂度会很高,对于大的数组来说不可行。于是我想到了利用前缀和的性质来优化这个过程。具体来说,就是统计前缀和对3和5的模结果相同的出现次数,同时排除那些对4的模结果相同的次数。
我可以用三个数组或者哈希表分别来记录前缀和对3、5、4取模后的结果出现的次数。在这之后,遍历前缀和数组,对于每个位置i,我需要计算在i之前,有多少个前缀和满足模3和模5的结果与当前前缀和相同,但模4的结果不同,这样才能保证子数组的和满足条件。这个过程的关键在于如何高效地统计这些符合条件的前缀和的数量。通过哈希表,可以在常数时间内查询到某个模结果出现的次数,从而快速计算出满足条件的子数组数量。
总的来说,这个解法的时间复杂度是O(n),因为只需要遍历数组几次,每次遍历的操作都是常数时间的。空间复杂度也是O(n),因为需要存储前缀和以及相关的哈希表数据。这样的效率对于题目中的数据范围(数组长度可达到1e5)是完全可行的。需要注意的一点是,初始时前缀和为0的情况也需要考虑进去,因为子数组可能从第一个元素开始,这时候对应的前缀和是0,这个也需要在哈希表中进行初始化。
三、代码
这个算法的时间复杂度是O(n),因为我们需要遍历数组一次来计算前缀和,然后再遍历一次来统计满足条件的子数组数量。空间复杂度是O(n),因为我们需要存储前缀和以及三个哈希表。
n = int(input())
a = list(map(int, input().split()))prefix_sum = [0] * (n + 1)
for i in range(n):prefix_sum[i + 1] = prefix_sum[i] + a[i]mod3 = [0] * (n + 1)
mod5 = [0] * (n + 1)
mod4 = [0] * (n + 1)
for i in range(n + 1):mod3[i] = prefix_sum[i] % 3mod5[i] = prefix_sum[i] % 5mod4[i] = prefix_sum[i] % 4count_mod3 = {}
count_mod5 = {}
count_mod4 = {}
for i in range(n + 1):if mod3[i] not in count_mod3:count_mod3[mod3[i]] = 0if mod5[i] not in count_mod5:count_mod5[mod5[i]] = 0if mod4[i] not in count_mod4:count_mod4[mod4[i]] = 0count_mod3[mod3[i]] += 1count_mod5[mod5[i]] += 1count_mod4[mod4[i]] += 1result = 0
for i in range(n + 1):count_mod3[mod3[i]] -= 1count_mod5[mod5[i]] -= 1count_mod4[mod4[i]] -= 1result += count_mod3[mod3[i]] * count_mod5[mod5[i]] - count_mod3[mod3[i]] * count_mod4[mod4[i]]print(result)
这个代码首先计算前缀和以及每个前缀和的模值,然后使用哈希表统计每个模值出现的次数。最后,通过遍历前缀和数组,统计满足条件的子数组数量。