连续子串和的整除问题 题解 | 豆包MarsCode AI刷题

127 阅读4分钟

连续子串和的整除问题 题解

题面

问题描述

小M是一个五年级的小学生,今天他学习了整除的知识,想通过一些练习来巩固自己的理解。他写下了一个长度为 n 的正整数序列 a_0, a_1, ..., a_{n-1},然后想知道有多少个连续子序列的和能够被一个给定的正整数 b 整除。你能帮小M解决这个问题吗?

测试样例

样例1:

输入:n = 3,b = 3,sequence = [1, 2, 3]
输出:3

样例2:

输入:n = 4,b = 5,sequence = [5, 10, 15, 20]
输出:10

样例3:

输入:n = 5,b = 2,sequence = [1, 2, 3, 4, 5]
输出:6

一、题目解析

对于连续序列和问题,我们很容易想到用一个前缀和数组来维护来实现 O(1)\text{O(1)} 查询一段序列和,粗略的解释就是,前缀和数组 pre[i]\text{pre[i]} 维护了从序列第 1\text{1}个数到第 i\text{i} 个数的和,当我们想要查询第 x\text{x} 个数到第 y\text{y} 个数的和时,只需要让 pre[y]pre[x-1]\text{pre[y]} - \text{pre[x-1]} 差值就是序列 x\text{x}y\text{y} 的和。

但是我们很快发现连续序列是不定长的,如果基于长度暴力枚举将达到 O(n\text{O(n}^2)\text{2)} 的时间复杂度,十分不优雅。

此时我暂时没有什么好思路,于是让豆包 AI\text{AI} 给了建议,发现并没有想到哈希表维护这个做法,即便是知道可以用哈希表我也一时间没想到怎么使得复杂度在序列遍历过程中线性解决问题,于是让 AI\text{AI} 给出进一步解释,这个做法真是出乎我意料的绝妙。

查找哈希表:如果哈希表中存在相同的余数,说明存在一些前缀和 prefixSum[j] 使得 (prefixSum[i] - prefixSum[j]) % b == 0,我们将这些情况的数量累加到 count 中。

显然,如果哈希表存在相同余数,我们利用类似前缀和的思维,此段减掉前一段有相同余数的序列段,剩下的序列就是能够整除 b\text{b} 的序列的和了,而相同余数的段数之前出现了多少,我们就能在此段下构造多少段整除序列段,所以正确性是显然的。

于是关键步骤呼之欲出:

  1. 初始化哈希表remainderCount.put(0, 1); 表示余数为 0 的情况已经出现了一次。
  2. 计算前缀和prefixSum[i] = prefixSum[i - 1] + sequence.get(i - 1); 计算当前位置的前缀和。
  3. 计算余数int remainder = prefixSum[i] % b; 计算当前前缀和的余数。
  4. 余数调整if (remainder < 0) remainder += b; 如果余数为负数,调整为正数。
  5. 查找哈希表if (remainderCount.containsKey(remainder)) { count += remainderCount.get(remainder); } 查找满足条件的 prefixSum[j]
  6. 更新哈希表remainderCount.put(remainder, remainderCount.getOrDefault(remainder, 0) + 1); 更新哈希表中当前余数的出现次数。

完整代码:

import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static int solution(int n, int b, List<Integer> sequence) {
        // Please write your code here
        int[] pre = new int[n + 1]; // 我们用pre数组记录前缀和
        Map<Integer, Integer> remainderCount = new HashMap<>(); // java封装的哈希表
        remainderCount.put(0, 1); // 初始化余数为0的情况

        int count = 0;

        // 计算前缀和,维护哈希表
        for (int i = 1; i <= n; i++) {
            pre[i] = pre[i - 1] + sequence.get(i - 1);
            int remainder = pre[i] % b; 
            // 取模剩余 remainder,取模后 remainder 取值范围为 (-b, b);
            // 如果 remainder 为负数,调整为正数
            if (remainder < 0) remainder += b;

            // 查找满足条件的 pre[j]
            if (remainderCount.containsKey(remainder)) {
                count += remainderCount.get(remainder);
            }

            // 更新哈希表
            remainderCount.put(remainder, remainderCount.getOrDefault(remainder, 0) + 1);
        }

        return count;
    }

    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);
    }
}

二、知识总结

1.前缀和:在题目解析部分我们已经做了较为详尽的解释。

2.哈希表(Hash Map)

哈希表是一种高效的数据结构,用于存储键值对,并支持快速的查找、插入和删除操作。在这个问题中,我们使用哈希表来记录每个前缀和模 b 的余数的出现次数。

  • Map<Integer, Integer> remainderCount 用于存储余数及其出现次数。
  • remainderCount.put(remainder, remainderCount.getOrDefault(remainder, 0) + 1) 更新哈希表中当前余数的出现次数。

三、学习计划

可以利用豆包MarsCode AI 刷题的算法分类的功能,挑选相近的题目多加练习巩固

四、工具运用

我们在学习到新算法新知识看不懂时,其实也可以丢给豆包 AI\text{AI} 再给我们解释一遍,是真的会有不同收获的。