问题描述
小明是一个五年级的小学生,今天他刚刚学习了整除,想练习一下自己的掌握情况。他在纸上写了一个长度为 n 的正整数序列,a_0, a_1,..., a_{n - 1},然后想了一个正整数 b,他想知道这个序列有多少个连续子串的和满足能够被 b 整除。你可以帮帮他吗?
输入格式
共两行,第一行包含两个正整数 n(1 <= n <= 10^5) 和 b(1 <= b <= 10^8),第二行包含 n 个正整数,表示 a_0, a_1,..., a_{n - 1}(1 <= a_i <= 10^4)。
输出格式
共 1 行,一个整数,表示这个序列有多少个连续子串的和满足能够被 b 整除。
输入样例 1
3 3
1 2 3
输出样例 1
3
数据范围
50%的数据:n(1 <= n <= 10^3)。
100%的数据:n(1 <= n <= 10^5)。
思路
问题:一个序列中有多少个连续子序列的和能够被给定的正整数 b 整除。
技巧:前缀和、哈希表。
前缀和
前缀和是一个数组,其中每个元素是原数组中从开始到当前位置的元素之和。
对于数组 a,其前缀和数组 s 定义为:s[i]=a[0]+a[1]+⋯+a[i−1]。注意,这里 s[0] 通常定义为 0
然而,在这个问题中,我们稍微修改一下前缀和的定义,使其包括当前元素:
s[i]=a[0]+a[1]+⋯+a[i]
这样,任意两个前缀和之差就是它们之间子序列的和:
s[j]−s[i]=(a[0]+a[1]+⋯+a[j])−(a[0]+a[1]+⋯+a[i−1])=a[i]+a[i+1]+⋯+a[j−1]
哈希表
我们将使用哈希表来存储每个前缀和模 b 的余数以及该余数出现的次数。若两个前缀和模b的余数相同,则这两个前缀和的差即二者之间的子序列和一定可以被b整除。
当我们遍历前缀和时,我们检查当前余数是否已经存在于哈希表中。如果存在,那么说明存在一个或多个之前的前缀和,与当前前缀和之间的差(即子序列的和)能够被 b 整除。
余数情况讨论
- 如果余数为负数(在 Java 中
%运算符可能产生负数余数),需要加上 b将其调整为正数。 - 如果余数为 0,说明从开始到当前位置的子序列和可以被 b 整除,计数器+1。
- 其余情况,检查哈希表中是否已经存在相同的余数。如果存在,说明存在一个或多个之前的子序列和与当前子序列和之间的差可以被 b 整除,
将余数出现的次数加到计数器上。(注意不要忘了更新哈希表中当前余数的计数,且该操作是在被标亮的操作后执行的)
代码展示
import java.util.HashMap;
import java.util.List;
public class Main {
public static int solution(int n, int b, List<Integer> sequence) {
// Please write your code here
HashMap<Integer,Integer> yushu=new HashMap<>();
int prefixsum=0;
int cnt=0;
int yu;
for(int i=0;i<n;i++){
prefixsum+=sequence.get(i);
yu=prefixsum%b;
if(yu<0){
yu+=b;
}
if(yu==0){
cnt++;
}
if(yushu.containsKey(yu)){
cnt+=yushu.get(yu);
}
yushu.put(yu,yushu.getOrDefault(yu,0)+1);
}
return cnt;
}
public static void main(String[] args) {
// You can add more test cases here
List<Integer> sequence = new ArrayList<>();
sequence.add(1);
sequence.add(2);
sequence.add(3);
System.out.println(solution(3, 3, sequence) == 3);
}
}