493. 连续子数组零尾数问题

76 阅读4分钟

题解:连续子数组满足条件的数量


问题分析

1. 问题目标

给定一个整数数组,我们需要计算其中的连续子数组,使得这些子数组的乘积末尾的零的数量大于等于 (x)

2. 问题核心

  • 末尾零的数量:一个数的乘积末尾零的数量由因子 (2) 和 (5) 的对数决定,因为每对 (2 \times 5) 产生一个零。因此,问题可转化为:
    • 对每个连续子数组,判断因子 (2) 和 (5) 的数量,是否满足 (\min(\text{因子} \ 2, \text{因子} \ 5) \geq x)。

3. 如何高效计算

  • 滑动窗口
    • 使用滑动窗口技巧,以固定右端点,动态调整左端点,确保窗口内的子数组满足条件。
    • 每次确定窗口后,直接统计以右端点为终点的所有合法子数组。

求解步骤

1. 辅助函数

  • 预处理每个数字中包含的 (2) 和 (5) 的因子数量。通过不断除以 (2) 或 (5) 统计数量。
  • 复杂度为 (O(\log(\text{num}))),实际运行效率较高。

2. 滑动窗口

  • 使用两个指针 (left) 和 (right),表示当前的窗口区间。
  • 对于每个右端点 (right),累积窗口内 (2) 和 (5) 的因子数量。
  • 如果满足 (\min(\text{因子} \ 2, \text{因子} \ 5) \geq x),调整左端点 (left),并计算所有以 (right) 为终点的合法子数组数量。

3. 取模操作

  • 因为结果可能很大,最终答案对 (10^9 + 7) 取模。

代码实现

以下是完整的 C++ 实现:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int MOD = 1e9 + 7;

// 辅助函数:计算数字中某因子的数量
int count_factors(int n, int factor) {
    int count = 0;
    while (n > 0 && n % factor == 0) {
        count++;
        n /= factor;
    }
    return count;
}

// 主函数:计算满足条件的连续子数组数量
int solution(vector<int> a, int x) {
    int n = a.size();
    vector<int> fives(n, 0), twos(n, 0);
    
    // 预处理:计算每个数中因子 2 和 5 的数量
    for (int i = 0; i < n; i++) {
        fives[i] = count_factors(a[i], 5);
        twos[i] = count_factors(a[i], 2);
    }

    // 滑动窗口
    int left = 0, result = 0;
    int total_fives = 0, total_twos = 0;

    for (int right = 0; right < n; right++) {
        // 累加当前右端点的因子数量
        total_fives += fives[right];
        total_twos += twos[right];

        // 移动左端点,确保窗口内满足条件
        while (min(total_fives, total_twos) >= x) {
            total_fives -= fives[left];
            total_twos -= twos[left];
            left++;
        }

        // 以当前 right 为终点的所有子数组均满足条件
        result = (result + left) % MOD;
    }

    return result;
}

// 测试用例
int main() {
    vector<int> a1 = {5, 2, 3, 50, 4};
    cout << (solution(a1, 2) == 6) << endl; // 输出:6

    vector<int> a2 = {10, 5, 2, 1};
    cout << (solution(a2, 3) == 0) << endl; // 输出:0

    vector<int> a3 = {25, 4, 8};
    cout << (solution(a3, 1) == 2) << endl; // 输出:2

    return 0;
}

代码解析

1. 预处理

  • 遍历数组中的每个数字,计算其中 (2) 和 (5) 的因子数量。
  • 使用 (O(n \cdot \log(\text{num}))) 时间完成。

2. 滑动窗口

  • 动态维护 (total_fives) 和 (total_twos) 作为当前窗口内因子 (2) 和 (5) 的数量总和。
  • 如果 (\min(total_fives, total_twos) \geq x),调整 (left) 确保满足条件。

3. 结果累加

  • 以每个 (right) 为右端点的合法子数组数量为 (left)。
  • 累加结果并对 (MOD = 10^9 + 7) 取模。

复杂度分析

  1. 时间复杂度

    • 预处理因子:(O(n \cdot \log(\text{num})))。
    • 滑动窗口:(O(n))。
    • 总复杂度:(O(n \cdot \log(\text{num})))。
  2. 空间复杂度

    • 额外数组存储因子:(O(n))。
    • 总复杂度:(O(n))。

测试分析

示例 1

  • 输入:(a = [5, 2, 3, 50, 4], x = 2)
  • 分析:
    • (5) 的因子数:([1, 0, 0, 2, 0])
    • (2) 的因子数:([0, 1, 1, 0, 2])
    • 滑动窗口结果:满足条件的子数组有 6 个。
  • 输出:6

示例 2

  • 输入:(a = [10, 5, 2, 1], x = 3)
  • 分析:
    • 乘积的末尾零不可能大于等于 3。
  • 输出:0

示例 3

  • 输入:(a = [25, 4, 8], x = 1)
  • 分析:
    • 滑动窗口统计满足条件的子数组数量为 2。
  • 输出:2

总结

  • 本题通过 滑动窗口 优化计算,保证效率。
  • 核心在于因子分解和子数组统计,算法简单高效。
  • 模块化设计便于理解和扩展,适合处理更复杂的数值问题。