连续子数组零尾数问题 | 豆包MarsCode AI刷题

114 阅读4分钟

题目描述

FF正在研究一个数组,并想要计算出其中的连续子数组的某种特性。给定一个整数数组,你需要编写一个函数来返回乘积末尾零的数量大于等于xx的连续子数组的数量。由于答案可能非常大,你需要将结果对 109+710^9 +7 取模后再返回。

输入

a=[5,2,3,50,4],x=2a=[5, 2, 3, 50, 4], x = 2

输出

66

算法

双指针, 分解因子

思路

我们需要找到一个整数数组中所有乘积末尾零的数量大于等于 x 的连续子数组数量。为了达到这一目标,可以注意到以下几点:

  1. 末尾零的来源:一个整数乘积的末尾零数量,取决于该数的 25 因子个数。因为每一对 25 的组合会生成一个末尾零。
  2. 因子数量的计算:因此,计算子数组乘积中 25 因子的数量可以确定末尾零的数量,即 min(2的个数, 5的个数)

步骤

我们可以将问题分解为以下几个步骤:

  1. 预处理每个数的因子个数

    • 遍历数组 a,对每个元素 a[i],计算其 25 因子的数量。
    • 这可以通过除法来实现,不断将 a[i] 除以 2,记录它的因子次数,直到 a[i] 不再能被 2 整除。对于 5 因子同理。
    • 将这些因子信息记录在一个辅助数组 v 中,v[i].first 表示 a[i]2 因子的数量,v[i].second 表示 5 因子的数量。
  2. 滑动窗口算法统计满足条件的子数组: 使用滑动窗口(或双指针)来快速统计满足条件的连续子数组数量。

    • 用两个指针 lr 代表滑动窗口的左右边界,r 为窗口的右边界,l 为左边界。
    • qp 分别表示窗口中所有元素的 25 因子总和。
    • 对于每个 r 的位置,更新窗口中的因子数量 qp,将 v[r].firstv[r].second 分别加到 qp 中。
  3. 满足条件的窗口处理

    • 判断当前窗口是否满足 min(q, p) >= x(即窗口内的 25 因子的最小值是否大于等于 x)。
    • 如果满足条件,则说明从 lr 的子数组满足条件,并且从 l 开始到 r 结束的所有连续子数组都会满足条件(这是因为 r 之后的每个数也会包含足够的 25 因子)。
    • 所以当窗口满足条件时,可以直接将 a.size() - r 个子数组计入满足条件的子数组数量中。
    • 然后将 l 向右移动,逐步减少窗口内 25 因子的数量,直到窗口不再满足条件。
  4. 返回答案

    • 滑动窗口遍历完成后,将计算出的满足条件的子数组数量 ans 返回(结果需要对 10^9 + 7 取模)。

复杂度分析

  1. 时间复杂度

    • 因子分解部分:遍历数组 a 的每个元素,计算其 25 的因子数量。对每个数因子分解的复杂度为 O(log a[i]),总复杂度为 O(n log a[i])
    • 滑动窗口部分:指针 r 每次右移一位,l 根据条件动态调整,因此总时间复杂度为 O(n)
    • 综合时间复杂度为 O(n log a[i])
  2. 空间复杂度

    • 辅助数组 v 和其他常量空间需求,总体空间复杂度为 O(n)

代码分析

 constexpr int mod = 1e9 + 7;
 ​
 int solution(std::vector<int> a, int x) {
     // Step 1: 预处理,计算每个元素的2和5因子数量
     std::vector<std::pair<int, int> > v(a.size());
     for (int i = 0; i < a.size(); i ++) {
         int xx = a[i];
         
         // 计算2因子数量
         while (xx % 2 == 0) {
             xx /= 2;
             v[i].first++;  // 记录2因子数量
         }
         
         // 计算5因子数量
         while (xx % 5 == 0) {
             xx /= 5;
             v[i].second++;  // 记录5因子数量
         }
     }
 ​
     // Step 2: 滑动窗口,统计满足条件的子数组数量
     int ans = 0;
     int l = 0, r = 0;
     int q = 0, p = 0;  // q和p分别累计窗口内2和5因子的数量
     
     while (r < a.size()) {
         // 增加当前右边界的因子数量
         q += v[r].first;
         p += v[r].second;
         
         // 如果当前窗口满足 min(q, p) >= x,则统计符合条件的子数组
         while (std::min(q, p) >= x) {
             ans = (ans + (a.size() - r)) % mod;  // 统计满足条件的子数组数量
             
             // 移动左边界,减少左端元素的因子数量
             q -= v[l].first;
             p -= v[l].second;
             l++;
         }
         
         // 移动右边界
         r++;
     }
     
     return ans;
 }

难点分析总结

  • 因子分解的逻辑:确保对每个数的 25 因子数量统计准确。
  • 滑动窗口的控制:要动态调整窗口边界,正确更新 qp 的值,使得 min(q, p) >= x 成立时统计子数组数量。
  • 符合条件的子数组数量:满足条件时可以一次性将 (a.size() - r) 个子数组计入答案,避免重复计算。