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

52 阅读4分钟

学习方法与心得:解析连续子数组乘积末尾零问题

一、题目解析与解决思路

题目背景

本问题要求统计一个整数数组中,乘积末尾零的数量大于等于 ( x ) 的连续子数组数量。这是一个结合因子分解滑动窗口技巧的复杂问题。其核心在于理解数字因子的分布特性,以及如何通过滑动窗口优化子数组统计。

关键问题拆解

1. 末尾零的计算

一个数的末尾零取决于其因子 ( 10 = 2 \times 5 ) 的数量。为了使末尾有 ( k ) 个零,一个数的因子 ( 2 ) 和 ( 5 ) 必须至少各有 ( k ) 个。因此,问题可以转化为:

  • 对于每个子数组,统计其中 ( 2 ) 和 ( 5 ) 因子的总数,取两者的较小值,判断是否满足 ( \geq x )。
2. 滑动窗口

在遍历数组时,用滑动窗口记录当前子数组的因子 ( 2 ) 和 ( 5 ) 的总数。滑动窗口的优势在于:

  • 动态调整窗口大小:通过扩大或缩小窗口范围,可以快速计算符合条件的子数组数量。
  • 降低复杂度:避免了对每个子数组逐一计算的高时间复杂度。

解题思路

1. 因子分解
  • 对每个数字进行分解,统计其因子 ( 2 ) 和 ( 5 ) 的个数,分别存储在数组中(如 twoCountfiveCount)。
2. 滑动窗口统计
  • 使用左右两个指针维护一个窗口,记录当前窗口的因子 ( 2 ) 和 ( 5 ) 的总数。
  • 当窗口内的因子总数满足 ( \min(\text{totalTwos}, \text{totalFives}) \geq x ) 时,统计满足条件的子数组数量。
3. 取模处理

由于答案可能非常大,需要对结果取模 ( 10^9 + 7 ),以防止溢出。

具体代码

private static final int MOD = 1000000007;

// 计算数字中因子 2 的个数
private static int countFactor(int num, int factor) {
    int count = 0;
    while (num % factor == 0 && num > 0) {
        count++;
        num /= factor;
    }
    return count;
}

public static int solution(int[] a, int x) {
    int n = a.length;
    int[] twoCount = new int[n]; // 因子2的计数数组
    int[] fiveCount = new int[n]; // 因子5的计数数组

    // 初始化因子2和因子5的计数
    for (int i = 0; i < n; i++) {
        twoCount[i] = countFactor(a[i], 2);
        fiveCount[i] = countFactor(a[i], 5);
    }

    // 滑动窗口
    int totalSubarrays = 0;
    int left = 0;
    int totalTwos = 0, totalFives = 0;

    for (int right = 0; right < n; right++) {
        // 加入右边界的数
        totalTwos += twoCount[right];
        totalFives += fiveCount[right];

        // 如果当前窗口中最小的因子2和因子5的个数满足 >= x,则统计子数组
        while (Math.min(totalTwos, totalFives) >= x) {
            // 子数组 [left, right] 的所有子数组都满足条件
            totalSubarrays += (n - right); // 计算以left开始的所有满足条件的子数组数
            totalSubarrays %= MOD;

            // 缩小窗口,减少左边界的影响
            totalTwos -= twoCount[left];
            totalFives -= fiveCount[left];
            left++;
        }
    }

    return totalSubarrays;
}

public static void main(String[] args) {
    System.out.println(solution(new int[]{5, 2, 3, 50, 4}, 2) == 6);
    System.out.println(solution(new int[]{10, 5, 2, 1}, 3) == 0);
    System.out.println(solution(new int[]{25, 4, 8}, 1) == 2);
}

二、知识总结与学习心得

1. 滑动窗口的优势

滑动窗口算法是解决子数组问题的利器,特别适用于:

  • 子数组满足某种条件的统计问题。
  • 需要高效调整子数组范围的场景。

在本问题中,滑动窗口通过动态调整窗口大小,避免了枚举所有子数组的暴力计算,大幅降低了时间复杂度。

2. 数字因子分解技巧

数字的因子分解是本题的基础,通过迭代除以 ( 2 ) 和 ( 5 ),快速统计因子数量。这种技巧也适用于:

  • 判断一个数是否是某个数的倍数。
  • 计算组合数或阶乘末尾零的数量。

3. 动态条件判断

在滑动窗口算法中,动态判断窗口是否满足条件是核心。例如:

  • ( \min(\text{totalTwos}, \text{totalFives}) \geq x ) 是本题的关键条件。
  • 满足条件后,计算以左边界开始的所有子数组数量。

三、学习计划与高效刷题方法

1. 强化滑动窗口训练

针对滑动窗口算法,制定以下学习计划:

  • 基础题目:如最长子串、和为目标值的子数组。
  • 进阶题目:如本问题涉及的子数组统计问题。

2. 数学知识结合

  • 掌握数字因子分解的技巧,理解数字的因子分布特性。
  • 结合实际问题,练习因子分解与动态统计的应用场景。

3. AI 辅助学习

  • 题解参考:通过 MarsCode AI 等工具学习多种解法,比较不同思路。
  • 代码优化:利用 AI 提供的优化建议,提高代码效率和逻辑清晰度。

四、学习建议

  • 理解问题本质:本题的核心在于末尾零的计算,拆解为因子分解问题后,整体思路变得更加清晰。
  • 实践滑动窗口:滑动窗口是解决动态范围统计问题的重要算法技巧,建议多加练习。
  • 善用工具:通过 AI 工具辅助学习,快速掌握复杂问题的解法。

通过系统化的训练和总结,我对滑动窗口算法与数字因子分解技巧有了更深入的理解,能够高效解决类似的复杂统计问题。