问题描述
小M是一个五年级的小学生,今天他学习了整除的知识,想通过一些练习来巩固自己的理解。他写下了一个长度为 n 的正整数序列 a_0, a_1, ..., a_{n-1},然后想知道有多少个连续子序列的和能够被一个给定的正整数 b 整除。你能帮小M解决这个问题吗?
问题分析
题目理解下来是比较简单的,但是如果仔细看的话会发现这道题目有很多做法。
- 首先要求出连续子串,对其进行取模运算,那么很容易看出要用前缀和作为核心运算进行求解
- 其次,再对其进行计数就可以得到答案
前缀和
前缀和算法是一种用于快速计算数组元素之和的技术。它通过预先计算数组中每个位置前所有元素的累加和,将这些部分和存储在一个新的数组中,从而在需要计算某个区间的和时,可以通过简单的减法操作得到结果,而不必重新遍历整个区间。对于此题请向下看
代码分析
int solution(int n, int b, std::vector<int> a) {
int c = 0;
for (int i = 0; i < n; i++) {
int s = 0;
for (int j = i; j < n; j++) {
s += a[j]; //前缀和核心代码
if (s % b == 0) {
c++;
}
}
}
return c;
}
- 看了这道题首先大家想到的应该都是暴力算法直接两层for循环加核心前缀和就完成了但是仔细理解还可以优化
int solution(int n, int b, std::vector<int> a) {
std::unordered_map<int, int> m;
int s = 0;
m[0] = 1;
int f = 0;
for (int i = 0; i < n; i++) {
s += a[i]; //前缀和核心代码
int c = s % b;
if (m.find(c) != m.end()) {
f += m[c];
}
m[c]++;
}
return f;
}
- 如果用数组进行储存数据后直接进行比较会发现可以直接少了一层循环,当然同样也可以用哈希表构建储存
int solution(int n, int b, std::vector<int> a) {
std::unordered_map<int, int> m;
int s = 0;
m[0] = 1;
int f = 0;
for (int i = 0; i < n; i++) {
s += a[i]; //前缀和核心代码
int c = s % b;
if (m.find(c) != m.end()) {
f += m[c];
}
m[c]++;
}
return f;
}
分析
- 首先,可以发现不管是哈希表还是数组时间复杂度都要更低
- 其次,这两者方式大同小异都是建表后先初始化,然后在循环中根据条件更新计数和数组或者哈希表就可以。
启示
一道题目有多种不同的方法,就好比这道,所以我们应该考虑全面,优化代码,选择合适的算法和数据结构,避免不必要的循环和重复计算,尽量减少时间和空间复杂度。