携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
题目链接:1175. 质数排列
题目描述
请你帮忙给从 1 到 n 的数设计排列方案,使得所有的「质数」都应该被放在「质数索引」(索引从 1 开始)上;你需要返回可能的方案总数。
让我们一起来回顾一下「质数」:质数一定是大于 1 的,并且不能用两个小于它的正整数的乘积来表示。
由于答案可能会很大,所以请你返回答案 模 mod 之后的结果即可。
提示:
示例 1:
输入:n = 5
输出:12
解释:举个例子,[1,2,5,4,3] 是一个有效的排列,但 [5,2,3,4,1] 不是,因为在第二种情况里质数 5 被错误地放在索引为 1 的位置上。
示例 2:
输入: n = 100
输出: 682289015
整理题意
题目给定一个正整数 n,让我们对 [1, n] 内的「质数」和「非质数」进行排列,排列的要求是:
- 质数只能在质数位置上
- 非质数只能在非质数位置上 让我们返回所有可能的排列方案数。
另外题目提示由于答案可能会很大,所以需要返回 模
mod之后的答案结果即可。
解题思路分析
由于质数的位置和非质数的位置是相互独立的,所以我们可以使用 乘法原理 将「所有质数都放在质数索引上的方案数」「所有非质数都放在非质数索引上的方案数」得到总的方案数。
那么如何求「所有质数都放在质数索引上的方案数」和「所有非质数都放在非质数索引上的方案数」呢,根据 组合数学 得知即求质数个数和非质数个数的阶乘即可。
进而问题转换为求 [1, n] 中质数的个数,也就是筛质数问题,筛质数的方法也有很多:
- 枚举筛质数
- 埃氏筛质数
- 线性筛质数 三种筛质数的时间复杂度排序为:线性筛质数 < 埃氏筛质数 < 枚举筛质数。
因为线性筛质数不属于笔试/面试范畴,所以不需要掌握。
那么我们就选择使用次优的 埃氏筛质数:
在之前介绍过 埃氏筛质数,可以直接查看,这里重点讲解该题的解法。
通过埃氏筛质数得到 [1, n] 的质数个数 num,那么非质数的个数就是 n - num。
具体实现
- 使用埃氏筛质数统计
[1, n]的质数个数num,从而得到非质数个数n - num。 - 使用组合数学计算「所有质数都放在质数索引上的方案数」和「所有非质数都放在非质数索引上的方案数」,即求质数个数和非质数个数的阶乘即可。
- 使用 乘法原理 计算总的方案数。「所有质数都放在质数索引上的方案数」「所有非质数都放在非质数索引上的方案数」
复杂度分析
- 时间复杂度:,求
n个数中质数个数的时间复杂度为 ,也就是埃氏筛质数的时间复杂度。阶乘的时间复杂度为 ,总的时间复杂度为 。 - 空间复杂度:,我们需要 的空间记录每个数是否为质数。
代码实现
class Solution {
public:
int numPrimeArrangements(int n) {
int mod = 1e9 + 7;
bool prime[n + 1];
//初始化为全部都是质数
for(int i = 0; i <= n; i++) prime[i] = 1;
// 0 和 1 不是质数
prime[0] = prime[1] = 0;
//线性筛质数
for(int i = 2; i * i <= n; i++){
if(prime[i]) for(int j = i * i; j <= n; j += i) prime[j] = 0;
}
//统计质数的个数
long long int num = 0;
for(int i = 2; i <= n; i++) if(prime[i]) num++;
//计算非质数的摆放方案数
long long int a = 1;
for(int i = 1; i <= n - num; i++) a = (a * i) % mod;
//计算质数的摆放方案数
long long int b = 1;
for(int i = 1; i <= num; i++) b = (b * i) % mod;
//乘法原理
return (a * b) % mod;
}
};
总结
- 该题本质是求范围内的质数个数,这里需要掌握埃氏筛质数以及埃氏筛质数的时间复杂度结论,无需掌握其时间复杂度的证明。
- 乘法原理 和 组合数学 也是该题的核心。
- 测试结果:
结束语
生活就像一只储钱罐,你投入的每一分努力都会累积下来,在未来的某一天,令你惊喜无比。不必去羡慕别人拥有的东西,沉下心来,每天付出一点努力,时间终会给你想要的答案。新的一天,加油!