小C的连续自然数乘积问题 | 豆包MarsCode AI刷题

180 阅读4分钟

题目描述

小S在学习素数因子的分解,她希望在[1,n]的范围内,找到一些连续的自然数,这些数的乘积最多包含k个不同的素因子。你的任务是帮助小S找到可以取的连续自然数的最大长度。连续的自然数指的是不能重复且相邻数的差为1的一组正整数,例如[2,3,4,5]和 [5,6,7]都是连续的取数。

输入

n = 10, k = 3

输出

6

算法

分解素因子 + 滑动窗口

思路

这道题的核心是从 [1, n] 的范围内找到满足最多包含 k 个不同素因子的最长连续子序列。


难点分析

  1. 素因子分解:需要对每个数进行素因子分解,以确定其不同的素因子集合。由于 n 可能较大,如何在较短时间内高效地分解出每个数的素因子并避免重复计算是一个挑战。
  2. 滑动窗口的使用:要求子序列是连续的,且长度尽可能长。通过滑动窗口动态维护满足条件的最长连续区间。这个窗口需要动态调整,以确保不同素因子的数量不超过 k
  3. 频率统计与窗口维护:在滑动窗口的移动过程中,需要维护窗口内素因子的数量,并动态更新每个素因子的出现次数。当不同素因子的数量超过 k 时,移出窗口左边的数,并更新素因子频率,直到满足条件。

解题思路

  1. 素因子分解函数 divide(x)

    • 该函数将 x 分解为不同的素因子。通过遍历从 2sqrt(x) 的因数,逐个检查是否是 x 的因子。若是,将其加入结果并移除相应的因数,直到 x 不可再被该因数整除。
    • 对于仍大于 1x(即剩余的数是素数),直接将其加入结果中。
    • 这样,我们可以得到数 x 的所有不同素因子。
  2. 滑动窗口的初始化和扩展

    • 定义两个指针 li,表示窗口的左端和右端,初始时都指向 1
    • 遍历 [1, n] 范围内的每一个数 i,并将其素因子加入窗口中,通过 std::map 记录素因子的出现次数。
  3. 调整窗口以满足条件

    • 在遍历过程中,若当前窗口中不同素因子的数量超过 k,说明窗口不符合条件,需通过右移左指针 l 来缩小窗口,直到不同素因子数量小于等于 k
    • 在每次缩小窗口时,移除 l 对应数的素因子,若某个素因子计数降为 0,则将其从窗口的计数中删除。
  4. 更新最大长度

    • 每次窗口满足条件时,计算当前窗口长度 i - l + 1,并更新最大长度 ans

复杂度分析

  1. 时间复杂度O(n * sqrt(n))

    • 函数 divide(x) 的时间复杂度为 O(sqrt(x)),在最坏情况下需要遍历 sqrt(x) 的因数。
    • 主函数 solution 中,右指针 i1 遍历到 n,对于每个 i 调用 divide(i) 来获取其素因子。
    • 在整体上,时间复杂度约为 O(n * sqrt(n))
  2. 空间复杂度O(n)

    • 使用了 std::map 来记录窗口中素因子的出现次数。由于最多可能包含 n 个素因子,因此空间复杂度为 O(n)

总结

  • 难点:在于使用滑动窗口结合素因子分解,以动态维护满足条件的最长窗口。每次窗口移动时要精确更新素因子的计数并确保不同素因子的数量在 k 以内。
  • 时间复杂度主要由素因子分解和滑动窗口的遍历决定,总体为 O(n * sqrt(n)),在多数情况下已经足够高效

代码解析

 constexpr int mod = 1e9 + 7;
 ​
 // 素因子分解函数
 std::vector<int> divide(int x) {
     std::vector<int> v;
     for (int i = 2; i * i <= x; i++) {
         if (x % i == 0) {
             v.emplace_back(i);
             while (x % i == 0) x /= i;
         }
     }
     if ((v.empty() || x > v.back()) && x != 1) {
         v.emplace_back(x);
     }
     return v;
 }
 ​
 // 主解题函数
 int solution(int n, int k) {
     int ans = 0;  // 最长满足条件的连续子序列长度
     std::map<int, int> map;  // 记录当前窗口内素因子的频率
     int l = 1;  // 左指针,表示窗口左端
 ​
     // 遍历 [1, n],右指针从左到右逐个移动
     for (int i = 1; i <= n; i++) {
         std::vector<int> factor = divide(i);  // 获取当前数 i 的素因子
         for (int f : factor) {
             map[f]++;  // 更新素因子频率
         }
 ​
         // 若当前窗口内不同素因子超过 k,移动左指针缩小窗口
         while (map.size() > k) {
             std::vector<int> x = divide(l++);  // 左端移出一个数
             for (int f : x) {
                 map[f]--;  // 减少对应素因子的计数
                 if (map[f] == 0) {
                     map.erase(f);  // 如果素因子频率为 0,移出
                 }
             }
         }
 ​
         // 更新最长连续子序列长度
         if (map.size() <= k) {
             ans = std::max(ans, i - l + 1);
         }
     }
     return ans;
 }