「数学::质数」分解质因子 / LeetCode 2521(C++)

概述

由算数基本定理,我们知道任意一个大于1的自然数可以表示为一些质数的乘积:

LeetCode 2521:

给你一个正整数数组 nums ,对 nums 所有元素求积之后,找出并返回乘积中 不同质因数 的数目。

注意:

  • 质数 是指大于 1 且仅能被 1 及自身整除的数字。
  • 如果 val2 / val1 是一个整数,则整数 val1 是另一个整数 val2 的一个因数。

示例 1:

复制代码
输入:nums = [2,4,3,7,10,6]
输出:4
解释:
nums 中所有元素的乘积是:2 * 4 * 3 * 7 * 10 * 6 = 10080 = 25 * 32 * 5 * 7 。
共有 4 个不同的质因数,所以返回 4 。

思路

质因子:若a可整除x,且a为质数,则a为x的质因子。

明确一件事:

如果x存在大于的质数,则最多只存在一个,否则两个大于的质因数相乘会大于x。

也就是说我们可以质枚举小于****的质因子,并在最后单独判断:

cpp 复制代码
int cnt[N]{};       //cnt[i] = n 表示i是x的n个质因子
for (int i = 2; i * i <= x; i++) 
    while(!(x % i)) x /= i, cnt[i]++;
if (x != 1)...      //意味着当前x是原始x的最后的那一个大质因子

也就是在循环内部不断消耗可能的质因数,在最后脱离循环时,如果 x!=1,则当前x也为原始x的那个大于****的质因数。

可以证明的是,while循环会保证x一定是被他的质因数消耗的。

例如,如果x不断被i=2消耗,则后续i=4时x已经无法再被消耗。

在循环条件中的i * i <= x看起来有点不对劲。由于while中x不断被消耗,这似乎不能保证i能够枚举到原始的终点**。** 这其实不要紧,我们来证明这一点**:**

设原始x = P1^(K1) * P2^(K2) * ... * Pn^(Kn) * Pt。

(Pi为一系列递增质数,Ki为对应的幂,Pt为唯一的大于根号****的质数)

那么当i = P1,while完全消耗P1时,问题就等价于分解剩余的y = P2^(K2) * ... * Pn^(Kn) * Pt

由于P2>P1,接下来for循环使i从P1增长到P2时完全正确的。依旧,只需要循环i * i小于等于y。

同理,后续的分解也是如此。

在最后,单独判断最后的x,如果它不等于1,则意味着这是那个大质因子。


算法过程

对于本题,题目要求我们分解整个数组的乘积,但是比起先全部相乘再分解,不如逐一分解每个数组元素。

cpp 复制代码
vector<int> cnt(1001);
    for (int num : nums){
        for (int i = 2; i <= 1000; i++){
            while (!(num % i)) 
                cnt[i]++, num /= i;
        }
    if (num != 1) cnt[num]++;
}

这种枚举有些低效,怎么优化呢?


优化方案

还记得欧拉筛吗?「数学::质数」埃氏筛|欧拉筛(埃拉托斯特尼筛法|线性筛法)/ LeetCode 204(C++)

预处理是个很好的工作,先获取范围内所有的质数的数组prim,这样for循环的循环次数就大大降低了。

cpp 复制代码
vector<int> cnt(N);
for (int num : nums){
    for (int i = 0; prim[i] * prim[i] <= num; i++){
        while (!(num % prim[i])) 
            cnt[prim[i]]++, num /= prim[i];
    }
    if (num != 1) cnt[num]++;
}
        

Code

cpp 复制代码
constexpr int N = 1e3 + 1;
bool not_prim[N]{};
vector<int> prim;
auto init = []() -> int {
    for (int i = 2; i < N; i++) {
        if (!not_prim[i]) prim.push_back(i);
        for (int j = 0; i * prim[j] < N; j++) {
            not_prim[i * prim[j]] = true;
            if (!(i % prim[j])) break;
        }
    }
    return 0;
}();
class Solution {
public:
    int distinctPrimeFactors(vector<int>& nums) {
        vector<int> cnt(N);
        for (int num : nums){
            for (int i = 0; prim[i] * prim[i] <= num; i++){
                while (!(num % prim[i])) 
                    cnt[prim[i]]++, num /= prim[i];
            }
            if (num != 1) cnt[num]++;
        }
        return count_if(cnt.begin(), cnt.end(), [](int num) -> bool {return num;});
    }
};

复杂度

时间复杂度: O(nlogn)//分解单个数为logn

空间复杂度: O(n)//预处理prim数组


总结

算术基本定理是初等数论中一条非常基本和重要的定理,它把对自然数的研究转化为对其最基本的元素------素数的研究。不觉得很酷吗?

相关推荐
一只月月鸟呀2 小时前
uni-app 开发 App 、 H5 横屏签名(基于lime-signature)
开发语言·前端·uni-app
SomeB1oody3 小时前
【Rust中级教程】2.10. API设计原则之受约束性(constrained) Pt.1:对类型进行修改、`#[non_exhaustive]`注解
开发语言·后端·性能优化·rust
Hello.Reader3 小时前
Rust 并发编程:使用消息传递进行线程间数据共享
c++·算法·rust
SomeB1oody3 小时前
【Rust中级教程】2.9. API设计原则之显然性(obvious) :文档与类型系统、语义化类型、使用“零大小”类型
开发语言·后端·rust
西域编娃4 小时前
图书馆系统源码详解
开发语言·后端·scala
石兴稳7 小时前
SSD 固态硬盘存储密度的分区
开发语言·javascript·数据库
88号技师7 小时前
2025年2月最新SCI-中华穿山甲优化算法Chinese Pangolin Optimizer-附Matlab免费代码
开发语言·算法·matlab·优化算法
Yang-Never7 小时前
OpenGL ES -> GLSurfaceView绘制点、线、三角形、正方形、圆(索引法绘制)
android·java·开发语言·kotlin·android studio
念九_ysl7 小时前
前端排序算法完全指南:从理论到实践
开发语言·javascript·算法·ecmascript
IT猿手7 小时前
智能优化算法:雪橇犬优化算法(Sled Dog Optimizer,SDO)求解23个经典函数测试集,MATLAB
开发语言·前端·人工智能·算法·机器学习·matlab