小Q的非素数和排列问题 | 豆包MarsCode AI刷题
这题很有意思,我的做法是使用了埃拉托色尼筛法进行素数的快速判断,然后用剪枝的回溯法进行全排列,原理在文中有介绍
摘要
本文介绍了一种结合埃拉托色尼筛法和回溯法的算法,用于统计满足相邻元素之和都不是素数的排列数量。通过埃筛快速生成素数表,并在回溯生成排列的过程中利用剪枝优化,提升了算法效率。时间复杂度由素数筛选和排列生成的组合决定,适合中小规模问题的求解。
问题描述
小C对排列问题很感兴趣,她想知道有多少个长度为 的排列满足任意两个相邻元素之和都不是素数。排列的定义如下:
- 长度为 的数组,包含从 到 的所有整数。
- 每个数字恰好出现一次。
示例
输入
n = 5n = 3n = 6
输出
- 输出:
4 - 输出:
0 - 输出:
24
算法分析
1. 素数筛选——埃拉托色尼筛法
埃拉托色尼筛法是一种经典的求素数算法,适用于在范围 内快速生成素数表。算法步骤如下:
- 初始化一个布尔数组 ,其中 表示数字 是否为素数。
- 将 和 标记为
False,因为 和 不是素数。 - 从 开始,遍历数组:
- 若 为
True,将其所有倍数标记为False。
- 若 为
- 遍历完成后, 中所有值为
True的位置即为素数。
复杂度分析
埃拉托色尼筛法的时间复杂度为:
在本问题中,我们需要筛选 范围内的素数,因为相邻元素的最大可能和为 。
2. 回溯法生成排列
回溯法概述
回溯法是一种用于构造和探索所有可能解的算法,适用于排列、组合、棋盘覆盖等问题。核心思想是:
- 从起始状态出发,逐步扩展解的路径。
- 若当前路径满足问题约束,继续探索;否则回溯并尝试其他路径。
- 通过递归实现,遍历所有可能的解空间。
剪枝优化
在生成排列的过程中,我们可以利用以下规则进行剪枝,减少无效的递归:
- 若当前排列路径 中,最后一个元素与待添加元素之和为素数,则剪枝。
- 若某元素已被使用,则剪枝。
算法步骤
- 初始化路径数组 和标记数组 。
- 从 到 尝试添加元素 :
- 若 已被使用或与路径末尾之和为素数,则跳过。
- 否则,将 加入路径,标记为已使用,递归调用下一步。
- 若路径长度等于 ,则记录为有效排列。
- 递归返回后,移除路径中的最后一个元素,并取消使用标记(回溯)。
算法实现
Python实现
def generate_primes(limit):
"""
使用埃拉托色尼筛法生成素数表。
返回一个布尔列表,is_prime[i] 表示 i 是否为素数。
"""
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
return is_prime
def backtrack(n, path, used, is_prime, count):
"""
使用回溯法生成排列并统计符合条件的数量。
- n: 总长度
- path: 当前排列路径
- used: 当前使用过的数字标记
- is_prime: 素数表
- count: 计数器
"""
if len(path) == n:
# 如果成功构造长度为 n 的排列,计数加 1
count[0] += 1
return
for i in range(1, n + 1):
if used[i]:
continue
# 检查当前数字与前一个数字的和是否为素数
if path and is_prime[path[-1] + i]:
continue
# 选择数字 i
used[i] = True
path.append(i)
# 递归生成下一个数字
backtrack(n, path, used, is_prime, count)
# 回溯
used[i] = False
path.pop()
def solution(n):
"""
主函数,计算符合条件的排列数量。
"""
# 特殊情况
if n == 1:
return 1
if n == 2:
return 0
# 生成素数表
is_prime = generate_primes(2 * n)
# 使用回溯法生成排列并计数
used = [False] * (n + 1) # 用于标记是否使用过某个数字
count = [0] # 记录符合条件的排列数量
backtrack(n, [], used, is_prime, count)
return count[0]
if __name__ == "__main__":
# 测试用例
print(solution(5)) # 输出:4
print(solution(3)) # 输出:0
print(solution(6)) # 输出:24
Go实现
package main
import (
"fmt"
)
// 生成素数表(埃拉托色尼筛法)
func generatePrimes(limit int) []bool {
isPrime := make([]bool, limit+1)
for i := 2; i <= limit; i++ {
isPrime[i] = true
}
for i := 2; i*i <= limit; i++ {
if isPrime[i] {
for j := i * i; j <= limit; j += i {
isPrime[j] = false
}
}
}
return isPrime
}
// 回溯法生成排列并统计符合条件的数量
func backtrack(n int, path []int, used []bool, isPrime []bool, count *int) {
if len(path) == n {
(*count)++
return
}
for i := 1; i <= n; i++ {
if used[i] {
continue
}
if len(path) > 0 && isPrime[path[len(path)-1]+i] {
continue
}
used[i] = true
path = append(path, i)
backtrack(n, path, used, isPrime, count)
used[i] = false
path = path[:len(path)-1]
}
}
func solution(n int) int {
if n == 1 {
return 1
}
if n == 2 {
return 0
}
isPrime := generatePrimes(2 * n)
used := make([]bool, n+1)
count := 0
backtrack(n, []int{}, used, isPrime, &count)
return count
}
func main() {
fmt.Println(solution(5)) // 输出:4
fmt.Println(solution(3)) // 输出:0
fmt.Println(solution(6)) // 输出:24
}