寻找能够被整除的连续子序列
本文为青训营笔记选择豆包MarsCode AI 刷题(代码练习)题库中寻找能被整除的连续子序列的代码与思路详解
引言
欢迎来到数字世界的奇妙探险!在这个世界里,每个数字都充满了神秘的力量,它们可以组合、拆分,甚至施展魔法。今天,我们将一起揭开一个隐藏在数字序列中的古老秘密:如何找到那些能够被神秘力量 b 整除的连续子序列。
想象一下,你手中有一串闪闪发光的数字宝石,它们串成了一条项链。但是,只有当这些宝石的总重量(数字之和)能够被一个神奇的数字 b 整除时,它们才会发出耀眼的光芒。你的目标是找出所有这样的宝石组合,让它们的光芒照亮整个夜空。
准备好了吗?让我们一起踏上这段旅程,揭开数字的秘密,发现算法的力量。在这个过程中,你不仅会学到如何解决问题,还会学会如何思考问题。让我们一起开始这场激动人心的探险吧!
在本教程中,我们将探讨两种方法来解决一个有趣的问题:给定一个正整数序列和一个正整数 b,我们需要找出序列中有多少个连续子序列的和能够被 b 整除。
问题描述
小M是一个五年级的小学生,他学习了整除的知识,并想通过一些练习来巩固自己的理解。他写下了一个长度为 n 的正整数序列 sequence,然后想知道有多少个连续子序列的和能够被一个给定的正整数 b 整除。MarsCode - 能够被整除的连续子序列
方法一:暴力解法
概念解释
暴力解法通过两层循环遍历所有可能的子序列,计算每个子序列的和,然后检查这个和是否能被 b 整除。
代码实现
#include <vector>
int solution(int n, int b, std::vector<int> sequence) {
std::vector<int> prefixSum(n);
prefixSum[0] = sequence[0];
int count = 0;
for(int i = 1; i < n; i++) {
prefixSum[i] = prefixSum[i - 1] + sequence[i];
}
for(int i = 0; i < n; i++) {
for(int j = 0; j <= i; j++) {
int sum = (j == 0) ? prefixSum[i] : prefixSum[i] - prefixSum[j - 1];
if(sum % b == 0) count++;
}
}
return count;
}
复杂度分析
- 时间复杂度:O(n^2),其中n是序列的长度。
- 空间复杂度:O(n),需要存储前缀和数组。
方法二:前缀和与模运算
概念解释
这种方法利用了前缀和与模运算的性质,可以高效地计算出有多少个连续子序列的和能够被给定的正整数整除。
代码实现
#include <vector>
#include <unordered_map>
int solution(int n, int b, std::vector<int> sequence) {
std::vector<int> prefixMod(n, 0);
int sum = 0, count = 0;
std::unordered_map<int, int> modCount;
prefixMod[0] = sequence[0] % b;
modCount[prefixMod[0]] = 1;
for (int i = 1; i < n; ++i) {
sum += sequence[i];
prefixMod[i] = (sum + b) % b; // 保证余数非负
if (modCount.find(prefixMod[i]) == modCount.end()) {
modCount[prefixMod[i]] = 0;
}
modCount[prefixMod[i]]++;
}
for (auto& pair : modCount) {
count += pair.second * (pair.second - 1) / 2;
if (pair.first == 0) {
count += pair.second;
}
}
return count;
}
复杂度分析
- 时间复杂度:O(n),其中n是序列的长度。
- 空间复杂度:O(n),需要存储前缀和模b的结果和哈希表。
结论:解锁算法之门,探索数字之谜
在这段旅程中,我们不仅解决了一个具体的问题——寻找能够被给定正整数整除的连续子序列,还深入探讨了两种截然不同的解题方法:直观的暴力解法和高效的前缀和与模运算方法。
这两种方法代表了算法设计中的两种重要思想:直接性和优化。
暴力解法:直观与教育
暴力解法以其直观性著称,它直接模拟了问题的求解过程。这种方法的优点在于易于理解和实现,尤其适合初学者用来建立对问题的基本理解。通过暴力解法,我们可以清晰地看到问题的本质,为更复杂的算法打下坚实的基础。然而,暴力解法在处理大规模数据时可能会遇到性能瓶颈,这提示我们在实际应用中需要寻求更高效的解决方案。
前缀和与模运算:效率与智慧
前缀和与模运算方法则展示了算法优化的魅力。通过巧妙地利用数学性质和数据结构,我们将问题转化为更易于处理的形式,从而实现了时间复杂度的显著降低。这种方法不仅提高了算法的效率,也减少了额外的空间消耗。它教会我们如何通过观察问题的特性来设计更优的算法,这是每个算法学习者都应该掌握的技能。
学习与实践
通过本教程的学习,我们希望你能够:
- 理解问题:深入理解问题的本质,是解决问题的第一步。
- 掌握基础:熟练掌握基本的算法和数据结构,为解决更复杂的问题打下基础。
- 学会思考:培养分析问题和设计算法的能力,而不仅仅是寻找标准答案。
- 实践应用:将学到的知识应用到实际编程中,通过实践来巩固和深化理解。