当前位置: 首页 > article >正文

「数学::质数」分解质因子 / 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存在大于\sqrt{x}的质数,则最多只存在一个,否则两个大于\sqrt{x}的质因数相乘会大于x。

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

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的那个大于\sqrt{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为唯一的大于根号\sqrt{x}的质数)

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

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

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

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


算法过程

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

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循环的循环次数就大大降低了。

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

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数组


总结

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


http://www.kler.cn/a/522443.html

相关文章:

  • Python3 【函数】:见证算法的优雅与力量
  • csapp2.4节——浮点数
  • 2025-01-28 - 通用人工智能技术 - RAG - 本地安装 DeepSeek-R1对话系统 - 流雨声
  • Node.js与MySQL模块结合:打造安全高效的用户信息管理系统
  • MySQL(单表访问)
  • MongoDB常见的运维工具总结介绍
  • 算法1-1 模拟与高精度
  • 35、【OS】【Nuttx】OSTest分析(1):stdio测试(五)
  • 青少年CTF练习平台 贪吃蛇
  • 函数与方法
  • 浅谈OceanBase旁路导入
  • 如何学习Java后端开发
  • js手撕 | 使用css画一个三角形 使用js修改元素样式 驼峰格式与“-”格式相互转化
  • QT交叉编译环境搭建(Cmake和qmake)
  • MCP Server 开发实战:无缝对接 LLM 和 Elasticsearch
  • 【深度学习】常见模型-自编码器(Autoencoder, AE)
  • python -m pip和pip的主要区别
  • 亚博microros小车-原生ubuntu支持系列:14雷达跟踪与雷达守卫
  • CAN波特率匹配
  • OPPO自研DataFlow架构与实践
  • RHEL封闭环境部署zabbix
  • 【数据资产】数据资产管理概述
  • Workerman和Swoole有什么区别
  • Verilog中if语句和case语句综合出的电路区别
  • RabbitMQ 多种安装模式
  • 【信息系统项目管理师-选择真题】2013下半年综合知识答案和详解