剪枝小Q的非素数和排列问题 | 豆包MarsCode AI刷题

123 阅读3分钟

小Q的非素数和排列问题

问题描述

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


测试样例

样例1:

输入:n = 5​
输出:4​

样例2:

输入:n = 3​
输出:0​

实现思路

  1. 排列问题的基本要求:因为排列包含从1​n​ 的所有整数,而且每个数字恰好出现一次,因此排列的所有元素都是 1​ n​ 的一个全排列。

  2. 素数判断:需要定义一个判断素数的函数。判断一个数字是否为素数,即判断该数字是否只能被 1​ 和自身整除。

  3. 排列条件: 对于每一个排列,检查任意两个相邻数字之和是否是素数。如果是素数,则该排列不符合条件;如果所有相邻的数字之和都不是素数,则该排列符合条件。

  4. 回溯算法的应用:使用回溯算法生成所有可能的排列。对于每一个排列,当我们选择一个数字时,检查该数字与前一个数字之和是否是素数。如果是素数,跳过该选择;如果不是素数,继续选择下一个数字。

  5. 优化:由于问题规模较大,使用回溯时可以通过剪枝来减少不必要的计算。如果当前选择的数字与前一个数字之和已经是素数,则可以立即返回,不继续扩展当前路径。

实现步骤

  1. 生成排列:对于从1​n​的整数,可以使用回溯法来生成所有排列。每次选择一个数字,并确保这个数字未被使用。

  2. 检查相邻数字和是否为素数:在生成排列时,检查当前数字与前一个数字之和是否为素数。如果是素数,剪枝,跳过这个排列。

  3. 计数符合条件的排列: 对于每个符合条件的排列,将计数器加一,最后返回符合条件的排列数量。

def is_prime(num):
    if num <= 1:
        return False
    if num == 2:
        return True
    if num % 2 == 0:
        return False
    for i in range(3, int(num**0.5) + 1, 2):
        if num % i == 0:
            return False
    return True

def backtrack(n, path, used, result):
    if len(path) == n:
        result.append(path[:])
        return
  
    for i in range(1, n + 1):
        if not used[i]:
            if path and is_prime(path[-1] + i):
                continue  # 剪枝:如果当前元素与前一个元素之和是素数,跳过
            used[i] = True
            path.append(i)
            backtrack(n, path, used, result)
            path.pop()
            used[i] = False

def solution(n: int) -> int:
    result = []
    used = [False] * (n + 1)
    backtrack(n, [], used, result)
    return len(result)

时间复杂度

  1. 回溯的复杂度:回溯的时间复杂度主要取决于排列的生成过程。对于 n​ 个元素,共有 n!​ 种排列组合。每次递归会尝试每一个未使用的数字,因此生成所有排列的时间复杂度为 O(n!)​。
  2. 素数判断的复杂度:对于每个排列中的每一对相邻数字,is_prime​ 函数的时间复杂度是 O(sqrt(num))​。在最坏情况下,对于每个排列中的每对相邻数字,is_prime​ 函数的复杂度为 O(sqrt(n))​

所以,整体的时间复杂度是 O(n! * sqrt(n))​