「数学::质数」分解质因子 / 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数组


总结

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

相关推荐
M_Lin_14 分钟前
C++继承 多态
开发语言·c++
猿周LV21 分钟前
多线程进阶 : 八股文面试题 一 [Java EE 多线程 锁和死锁相关问题]
java·开发语言·java-ee
enyp8042 分钟前
qt QTreeWidget`总结
开发语言·数据库·qt
啾啾Fun2 小时前
[java基础-JVM篇]2_垃圾收集器与内存分配策略
java·开发语言·jvm
羊小猪~~2 小时前
基于C++“简单且有效”的“数据库连接池”
java·开发语言·前端·数据库·c++·后端·adb
belong_to_you3 小时前
【Python模块】——pymysql
开发语言·python
Hello__nibuhao3 小时前
C 语言中控制文件的读取或写入光标
c语言·开发语言·算法
思茂信息3 小时前
CST直角反射器 --- A求解器, 远场源, 距离像, 逆ChirpZ变换(ICZT)
开发语言·javascript·人工智能·算法·ai·软件工程·软件构建
Lin_Coder_3 小时前
Python入门 — 类
开发语言·python
似水এ᭄往昔3 小时前
【c语言】字符函数和字符串函数(1)
c语言·开发语言