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

73 阅读4分钟

问题描述

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


测试样例

样例1:

输入:n = 5
输出:4

样例2:

输入:n = 3
输出:0

样例3:

输入:n = 6
输出:24

题目解析:

1.明确素数的定义: 素数是大于1的正整数,且只能被1和它自身整除。

2.明确排列的定义: 长度为n的排列是从1到n的所有整数的一个排列组合。
但是题目要求在所有可能的排列中,过滤掉不满足“任意两个相邻元素之和都不是素数”的排列,统计剩下的排列数量。

思路分析:

  1. 判断素数
    编写一个函数is_prime判断一个数是否为素数
  2. 构建非素数图
    使用一个图结构存储每个数字i和其相邻元素的关系。
    如果i+j不是素数,则认为i和j之间有一条边。
  3. 回溯搜索合法排列
    使用递归+回溯方法,从任意一个数字开始,逐步构建排列。
    在每一步,检查当前数字与排列末尾数字的和是否为非素数。
  4. 剪枝优化
    如果当前路径已不合法,立即停止搜索,减少不必要的计算。

解决方案:

  1. 素数判断函数:判断一个数字是否为素数。
  2. 构建非素数图:创建一个邻接表,记录从1到n每个数字可与哪些数字相邻。
  3. 回溯搜索排列:使用回溯算法生成所有可能的排列,并在过程中检查是否满足条件。

代码实现:

def is_prime(num):
    """判断一个数是否是素数"""
    if num < 2:
        return False
    for i in range(2, int(num ** 0.5) + 1):
        if num % i == 0:
            return False
    return True

def build_non_prime_graph(n):
    """构建图,找出哪些数字的和是非素数"""
    non_prime_pairs = {}
    for i in range(1, n + 1):
        non_prime_pairs[i] = set()
        for j in range(1, n + 1):
            if not is_prime(i + j):
                non_prime_pairs[i].add(j)
    return non_prime_pairs

def solution(n: int) -> int:
    """使用回溯法计算满足条件的排列数量"""
    graph = build_non_prime_graph(n)
    visited = [False] * (n + 1)  # 记录哪些数字已被使用
    count = 0  # 统计满足条件的排列数量

    def backtrack(path):
        nonlocal count
        if len(path) == n:
            count += 1
            return
        for next_num in range(1, n + 1):
            # 检查未访问过的数字,且与当前路径尾部数字相邻和非素数
            if not visited[next_num] and (not path or next_num in graph[path[-1]]):
                visited[next_num] = True  # 标记为已访问
                backtrack(path + [next_num])  # 递归搜索
                visited[next_num] = False  # 回溯,撤销标记

    backtrack([])
    return count

代码解释:

  1. is_prime 函数:通过试除法判断一个数字是否为素数。

  2. build_non_prime_graph 函数 :创建一个邻接表,记录从1到n的每个数字可以与哪些数字相邻。

  3. 回溯算法
    递归地尝试每个数字。
    如果当前排列满足条件,则继续尝试下一个数字。
    如果排列长度达到n,则计数器加一。

  4. 优化:每次只尝试合法的数字组合,减少不必要的排列生成。

示例测试:

  1. 样例1

    print(solution(n = 5))  # 输出 4
    
  2. 样例2

    print(solution(n = 3)) # 输出 0
    
  3. 样例3

    print(solution(n = 6)) # 输出 24
    

时间复杂度:

  • 图构建复杂度O(n2)O(n^2),需要枚举1到n的所有数字对。
  • 回溯搜索复杂度O(n!)O(n!),在最坏情况下,需要生成所有排列。

因此,整体时间复杂度为O(n2+n!)O(n^2+n!)

空间复杂度:

  • 存储非素数图O(n2)O(n^2)邻接表存储每个数字与其相邻的数字集合。
  • 递归深度O(n)O(n)最大递归深度为排列长度。

因此空间复杂度为 O(n2)O(n^2)

总结

  1. 本题通过图的构建将排列问题转化为图问题,简化了相邻数字和的判定。

  2. 使用回溯算法有效地枚举合法排列,并结合剪枝优化减少搜索空间。

  3. 虽然时间复杂度较高,但对于n≤10的问题,运行效率可以接受。