观前提醒:本人刚刚接触算法,可能会有讲得不好的地方,请各位大佬多多指教
题目如下:
小M是一个五年级的小学生,今天他学习了整除的知识,想通过一些练习来巩固自己的理解。他写下了一个长度为 n 的正整数序列 a_0, a_1, ..., a_{n-1},然后想知道有多少个连续子序列的和能够被一个给定的正整数 b 整除。你能帮小M解决这个问题吗?
思路分析:
1、看到这道题其实很容易想到的就是暴力解法,就是用三层循环,分别枚举(子串长度,子串起始位置,以及子串求和),这当然是一种做法,但是明显时间复杂度很高O(n^3),会超时。那么有什么办法能够减少时间复杂度呢?那就要用到我们下面即将介绍的前缀和了。
2、在开始正式介绍题解的之前,先给还不知道前缀和的同学简单介绍一下什么是前缀和。 在前缀和算法中,我们首先计算一个前缀和数组S,其中S[i] 表示原数组a中从a[1] 到a[i] 的和。这样,如果我们想要得到从l到r的区间和,只需要计算S[r] - S[l-1] 即可。这种方法的优势在于,无论区间多大,求区间和的时间复杂度都是O(1) 。
3、前缀和的计算如下:S[i] = S[i - 1] + a[i]。注意:在初始化前缀和数组的时候下标应该从1开始,避免下标越界。
4、看完上述分析之后,想必大家都有了一定的思路了,我们先需要遍历数组以此来初始化前缀和数组。之后,我们可以通过遍历子数组(1到n)的长度,求出区间和来观察是否能够被数字整除。这样可以把时间复杂度降为O(n^2),减少了时间复杂度。
通过上述分析,我们可以看到,解决这个问题的关键在于利用前缀和来快速计算区间和,并通过统计每个前缀和对 b 取模后的结果出现的次数,来快速得到能够被 b 整除的子序列数量。这种方法不仅提高了算法的效率,而且也让我们对前缀和的应用有了更深的理解。希望本篇题解能够对大家有所帮助。
详细代码如下(仅供参考):
#include #include using namespace std; int solution(int n, int b, std::vector sequence) { // Please write your code here vector sum(n + 1); sum[0] = 0; for(int i = 1; i <= n; i++) { sum[i] = sum[i - 1] + sequence[i - 1];//前缀和数组 } int cnt = 0; for(int i = 1; i <= n; i++) { for(int j = i; j <= n; j++) { if((sum[j] - sum[j - i]) % b == 0) cnt++; } } return cnt; }
int main() { // You can add more test cases here std::vector sequence = {1, 2, 3}; std::cout << (solution(3, 3, sequence) == 3) << std::endl; return 0; }