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

3 阅读4分钟

一、问题描述

小C的连续自然数乘积问题:小S在学习素数因子的分解,她希望在 [1,n] 的范围内,找到一些连续的自然数,这些数的乘积最多包含k个不同的素因子。你的任务是帮助小S找到可以取的连续自然数的最大长度。

连续的自然数指的是不能重复且相邻数的差为1的一组正整数,例如 [2, 3, 4, 5][5, 6, 7] 都是连续的取数。

测试样例

样例1:

输入:n = 10,k = 3
输出:6

样例2:

输入:n = 20,k = 5
输出:12

样例3:

输入:n = 100,k = 4
输出:10

二、题意理解

输入:这题给出两个参数:n是指1到n的范围内找一个连续乘积的范围;k则是指这个连续乘积,分解因式后因子中是素数的种类不能超过k种素数。

输出:最大连续的长度

暴力法是两层循环遍历确认所有的出现的左右边界的可能性。优化方法是运用滑动窗口的思想,在条件合适情况下左边界向右移动。

不管怎么样都要用HashSet记录素数种类。

三、暴力法解析

(一)整体思路

两层循环,遍历每一种情况的左右边界。

每一种情况下,分解质因数,HashSet记录素数,超过k个阈值就break,然后不断更新maxLength为最终输出的答案。

(二)代码实现

import java.util.HashSet;
import java.util.Set;

public class Main {
    public static int solution(int n, int k) {
        int maxLength = 0;

        // 遍历以每个数 i 为起点的连续序列
        for (int i = 1; i <= n; i++) {
            Set<Integer> uniquePrimes = new HashSet<>();
            int length = 0;

            for (int j = i; j <= n; j++) {
                length++;

                // 找当前数 j 的所有素因子
                findPrimeFactors(j, uniquePrimes);

                // 如果素因子数大于 k,结束当前起点的遍历
                if (uniquePrimes.size() > k) {
                    break;
                }

                // 更新最大长度
                maxLength = Math.max(maxLength, length);
            }
        }

        return maxLength;
    }

    // 查找一个数的素因子并将其加入集合
    private static void findPrimeFactors(int num, Set<Integer> primeSet) {
        for (int i = 2; i * i <= num; i++) {
            while (num % i == 0) {
                primeSet.add(i);
                num /= i;
            }
        }
        if (num > 1) {
            primeSet.add(num);
        }
    }

    public static void main(String[] args) {
        System.out.println(solution(10, 3) == 6);
        System.out.println(solution(20, 5) == 12);
        System.out.println(solution(100, 4) == 10);
    }
}



(三)复杂度

时间复杂度: O(n52)O(n^{\frac{5}{2}})

内外两层循环是O(n2)O(n^{2}),然后考虑findPrimeFactors 方法调用。在每一次内层循环迭代时,都会调用一次 findPrimeFactors 方法来查找当前数 j 的素因子并添加到集合中。在 findPrimeFactors 方法内部,有一个循环来查找素因子,循环条件是 i * i <= num,这个循环执行的次数大致是O(n12)O(n^{\frac{1}{2}})。于是乘积得到O(n52)O(n^{\frac{5}{2}})

空间复杂度: O(n)O(n^{})

数组长度n


四、滑动窗口

(一)整体思路

left是窗口左端,right窗口右端,主循环是right<=n

辅助函数findPrimeFactors:比暴力法多一个参数,是用来右边界右移或者左边界右移,第3个参数为true是右边界右移1次,false就算左边界右移1次。

主要函数solution:首先定义变量,maxLength用于记录找到的满足条件的连续自然数的最大长度,初始化为 0。uniquePrimes是一个HashSet集合,存储当前窗口内数的不同素因子。leftright分别表示滑动窗口的左右边界,初始都设为 1。

然后通过一个外层的while循环,只要right<=n,就不断向右扩展窗口。在每次循环中:

-   先调用`findPrimeFactors`方法添加右边界数`right`的素因子到`uniquePrimes`集合中(通过传入`true`参数表示添加操作)。
-   接着检查`uniquePrimes`集合中素因子的个数,如果超过了`k`,就通过一个内层的`while`循环不断移动左边界,每次移动左边界时,调用`findPrimeFactors`方法移除左边界数`left`的素因子(传入`false`参数表示移除操作),直到`uniquePrimes`集合中素因子的个数不超过`k`为止。
-   最后,更新`maxLength`的值,取当前`maxLength``right - left + 1`(当前窗口大小)中的较大值。

循环结束后,返回maxLength,即为满足条件的连续自然数的最大长度。

(二)代码实现

import java.util.HashSet;
import java.util.Set;

public class Main {
    public static int solution(int n, int k) {
        int maxLength = 0;
        Set<Integer> uniquePrimes = new HashSet<>();
        int left = 1;
        int right = 1;

        while (right <= n) {
            // 添加右边界数的素因子
            findPrimeFactors(right, uniquePrimes, true);

            // 如果素因子数超过 k,则移动左边界直到满足条件
            while (uniquePrimes.size() > k) {
                findPrimeFactors(left, uniquePrimes, false);
                left++;
            }

            // 更新最大长度
            maxLength = Math.max(maxLength, right - left + 1);
            right++;
        }

        return maxLength;
    }

    // 查找一个数的素因子并将其加入或移除集合
    private static void findPrimeFactors(int num, Set<Integer> primeSet, boolean add) {
        for (int i = 2; i * i <= num; i++) {
            while (num % i == 0) {
                if (add) {
                    primeSet.add(i);
                } else {
                    primeSet.remove(i);
                }
                num /= i;
            }
        }
        if (num > 1) {
            if (add) {
                primeSet.add(num);
            } else {
                primeSet.remove(num);
            }
        }
    }

    public static void main(String[] args) {
        System.out.println(solution(10, 3) == 6);
        System.out.println(solution(20, 5) == 12);
        System.out.println(solution(100, 4) == 10);
    }
}

(三)复杂度

时间复杂度: O(n32)O(n^{\frac{3}{2}})

外层的 while 循环,循环条件是 right <= n,这意味着循环最多会执行 n 次,也就是O(n1)O(n^{1})。外层循环内调用findPrimeFactors ,在 findPrimeFactors 方法内部,循环来查找素因子,循环条件是 i * i <= num,次数是O(n12)O(n^{\frac{1}{2}})。所以乘积得到时间复杂度是 O(n32)O(n^{\frac{3}{2}})

空间复杂度: O(n)O(n^{})

数组长度n