小Q的非素数排列和排列问题[埃氏筛] | 豆包MarsCode AI刷题

53 阅读4分钟

小Q的非素数排列和排列问题

问题描述

小Q对排列很感兴趣,她想知道有多少个长度为n的排列满足任意两个相邻元素之和都不是素数。排列定义为一个长度为n的数组,其中包含从1到n的所有整数,每个数字恰好出现一次。


测试样例

样例1:

输入:n = 5
输出:4

样例2:

输入:n = 3
输出:0

样例3:

输入:n = 6
输出:24

要解决这个问题,我们需要找到所有长度为 n 的排列,使得任意两个相邻元素之和都不是素数。

解题思路:

1. 理解问题

  • 排列:一个长度为 n 的数组,包含从 1 到 n 的所有整数,每个数字恰好出现一次。
  • 相邻元素之和不是素数:对于排列中的任意两个相邻元素 a 和 ba + b 不能是素数。

2. 数据结构选择

  • 使用数组来存储排列。
  • 使用集合或数组来存储素数,以便快速判断两个数的和是否为素数。

3. 算法步骤

  1. 生成所有排列:可以使用递归或库函数(如 itertools.permutations)来生成所有可能的排列。
  2. 检查相邻元素之和:对于每个排列,检查相邻元素之和是否为素数。
  3. 统计符合条件的排列:如果一个排列的所有相邻元素之和都不是素数,则计数加一。

4. 素数判断

  • 可以使用埃拉托斯特尼筛法预先计算出所有小于 2n 的素数,因为两个数的和最大为 2n-1

5. 优化

  • 在生成排列时,可以尽早剪枝,即在生成排列的过程中,如果发现某两个相邻元素之和是素数,则停止生成该排列。

代码实现 Key point:

1. 生成所有排列

我们可以使用 itertools.permutations 来生成所有可能的排列。

2. 素数判断

我们可以使用埃拉托斯特尼筛法预先计算出所有小于 2n 的素数。

3. 检查相邻元素之和

对于每个排列,检查相邻元素之和是否为素数。

4. 统计符合条件的排列

如果一个排列的所有相邻元素之和都不是素数,则计数加一。

埃拉托斯特尼筛法是一种高效的算法,用于生成所有小于某个上限 limit 的素数。以下是埃拉托斯特尼筛法的实现细节:

埃拉托斯特尼筛法实现步骤

  1. 初始化:创建一个布尔数组 is_prime,大小为 limit + 1,并将所有元素初始化为 True。数组的索引表示数字,is_prime[i] 表示数字 i 是否为素数。

  2. 标记非素数:从 2 开始,将每个素数的倍数标记为 False。具体步骤如下:

    • 对于每个数字 i(从 2 到 limit),如果 is_prime[i] 为 True,则 i 是素数。
    • 将 i 的所有倍数(即 i * 2i * 3, ...)标记为 False
  3. 收集素数:遍历 is_prime 数组,将所有值为 True 的索引收集起来,这些索引就是小于 limit 的素数。

def sieve(limit):
    is_prime = [True] * (limit + 1)
    is_prime[0] = is_prime[1] = False  # 0 和 1 不是素数

    for i in range(2, int(limit**0.5) + 1):
        if is_prime[i]:
            for j in range(i * i, limit + 1, i):
                is_prime[j] = False

    primes = [num for num, prime in enumerate(is_prime) if prime]
    return primes

解释

  • is_prime[0] = is_prime[1] = False:0 和 1 不是素数。
  • for i in range(2, int(limit**0.5) + 1):只需要检查到 sqrt(limit),因为如果一个数 n 不是素数,它必定有一个因子小于或等于 sqrt(n)
  • for j in range(i * i, limit + 1, i):从 i * i 开始标记 i 的倍数为 False,因为 i * k(其中 k < i)已经在之前的循环中被标记过了。

假设我们要生成所有小于 20 的素数:

primes = sieve(20)
print(primes)  # 输出: [2, 3, 5, 7, 11, 13, 17, 19]

完整代码:

from itertools import permutations

def solution(n: int) -> int:
    # 使用埃拉托斯特尼筛法生成所有小于 2n 的素数
    def sieve(limit):
        is_prime = [True] * (limit + 1)
        is_prime[0] = is_prime[1] = False  # 0 和 1 不是素数

        for i in range(2, int(limit**0.5) + 1):
            if is_prime[i]:
                for j in range(i * i, limit + 1, i):
                    is_prime[j] = False

        primes = [num for num, prime in enumerate(is_prime) if prime]
        return primes

    # 生成所有小于 2n 的素数
    primes = sieve(2 * n)

    # 生成所有排列
    all_permutations = permutations(range(1, n + 1))

    # 统计符合条件的排列数量
    count = 0
    for perm in all_permutations:
        # 检查相邻元素之和是否为素数
        valid = True
        for i in range(n - 1):
            if perm[i] + perm[i + 1] in primes:
                valid = False
                break
        if valid:
            count += 1

    return count

if __name__ == '__main__':
    print(solution(n = 5) == 4)
    print(solution(n = 3) == 0)
    print(solution(n = 6) == 24)