likes
comments
collection
share

1175. 质数排列 : 乘法原理运用题

作者站长头像
站长
· 阅读数 14

题目描述

这是 LeetCode 上的 1175. 质数排列 ,难度为 简单

Tag : 「数学」、「组合数」、「二分」、「打表」

请你帮忙给从 111nnn 的数设计排列方案,使得所有的「质数」都应该被放在「质数索引」(索引从 111 开始)上;你需要返回可能的方案总数。

让我们一起来回顾一下「质数」:质数一定是大于 111 的,并且不能用两个小于它的正整数的乘积来表示。

由于答案可能会很大,所以请你返回答案 模 mod 109+710^9 + 7109+7 之后的结果即可。

示例 1:

输入:n = 5

输出:12
解释:举个例子,[1,2,5,4,3] 是一个有效的排列,但 [5,2,3,4,1] 不是,因为在第二种情况里质数 5 被错误地放在索引为 1 的位置上。

示例 2:

输入:n = 100

输出:682289015

提示:

  • 1<=n<=1001 <= n <= 1001<=n<=100

打表 + 二分 + 数学

根据题意,可将问题转换为求 nnn 以内的质数个数,记为 aaa,同时可得非质数个数为 b=n−ab = n - ab=na

质数的放置方案数为 a!a!a!,而非质数的放置方案数为 b!b!b!,根据「乘法原理」总的放置方案数为 a!×b!a! \times b!a!×b!

我们可以通过「打表」的方式将 100100100 以内的质数预处理到数组 list 中,对于每个 nnn 而言,我们找到第一个满足「值小于等于 nnn」的位置,从而得知 nnn 范围以内的质数个数。

代码:

class Solution {
    static int MOD = (int)1e9+7;
    static List<Integer> list = new ArrayList<>();
    static {
        for (int i = 2; i <= 100; i++) {
            boolean ok = true;
            for (int j = 2; j * j <= i; j++) {
                if (i % j == 0) ok = false;
            }
            if (ok) list.add(i);
        }
    }
    public int numPrimeArrangements(int n) {
        int l = 0, r = list.size() - 1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (list.get(mid) <= n) l = mid;
            else r = mid - 1;
        }
        int a = r + 1, b = n - a;
        long ans = 1;
        for (int i = b; i > 1; i--) ans = ans * i % MOD ;
        for (int i = a; i > 1; i--) ans = ans * i % MOD ;
        return (int)ans;
    }
}
  • 时间复杂度:二分的复杂度为 O(log⁡C)O(\log{C})O(logC),其中 C=25C = 25C=25100100100 以内的质数个数;计算方案数的复杂度为 O(n)O(n)O(n)。整体复杂度为 O(n)O(n)O(n)
  • 空间复杂度:O(C)O(C)O(C),其中 C=25C = 25C=25100100100 以内的质数个数

打表 + 数学

更进一步,对于特定的 nnn 而言,我们在预处理 100100100 以内的质数时,已经可以确定在 [1,n][1, n][1,n] 内有多少个质数,从而省掉二分操作。

使用数组 cnts 记录下不超过当前值范围内质数的个数,cnts[i]=xcnts[i] = xcnts[i]=x 含义为在 [1,i][1, i][1,i] 范围内质数数量为 xxx

代码:

class Solution {
    static int MOD = (int)1e9+7;
    static int[] cnts = new int[110];
    static {
        List<Integer> list = new ArrayList<>();
        for (int i = 2; i <= 100; i++) {
            boolean ok = true;
            for (int j = 2; j * j <= i; j++) {
                if (i % j == 0) ok = false;
            }
            if (ok) list.add(i);
            cnts[i] = list.size();
        }
    }
    public int numPrimeArrangements(int n) {
        int a = cnts[n], b = n - a;
        long ans = 1;
        for (int i = b; i > 1; i--) ans = ans * i % MOD ;
        for (int i = a; i > 1; i--) ans = ans * i % MOD ;
        return (int)ans;
    }
}
  • 时间复杂度:O(n)O(n)O(n)
  • 空间复杂度:O(C)O(C)O(C),其中 C=100C = 100C=100

最后

这是我们「刷穿 LeetCode」系列文章的第 No.1175 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉