一、问题描述
小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);
}
}
(三)复杂度
时间复杂度:
内外两层循环是,然后考虑findPrimeFactors
方法调用。在每一次内层循环迭代时,都会调用一次 findPrimeFactors
方法来查找当前数 j
的素因子并添加到集合中。在 findPrimeFactors
方法内部,有一个循环来查找素因子,循环条件是 i * i <= num
,这个循环执行的次数大致是。于是乘积得到
空间复杂度:
数组长度n
四、滑动窗口
(一)整体思路
left是窗口左端,right窗口右端,主循环是right
<=n
。
辅助函数findPrimeFactors
:比暴力法多一个参数,是用来右边界右移或者左边界右移,第3个参数为true是右边界右移1次,false就算左边界右移1次。
主要函数solution
:首先定义变量,maxLength
用于记录找到的满足条件的连续自然数的最大长度,初始化为 0。uniquePrimes
是一个HashSet
集合,存储当前窗口内数的不同素因子。left
和right
分别表示滑动窗口的左右边界,初始都设为 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);
}
}
(三)复杂度
时间复杂度:
外层的 while
循环,循环条件是 right <= n
,这意味着循环最多会执行 n
次,也就是。外层循环内调用findPrimeFactors
,在 findPrimeFactors
方法内部,循环来查找素因子,循环条件是 i * i <= num
,次数是。所以乘积得到时间复杂度是
空间复杂度:
数组长度n