1. 问题背景与核心挑战
这个算法问题描述了一个有趣的场景:在一个整数数组上,玩家从起点(索引0)出发,需要通过特定的移动规则到达终点(数组最后一个位置)。移动规则包含两种方式:常规的相邻位置移动和特殊的质数传送机制。
常规移动相对简单:可以从当前位置i移动到i-1或i+1(不越界的情况下)。而质数传送机制则复杂得多:如果当前位置的数字是质数p,可以立即跳转到数组中任意一个能被p整除的数字所在位置(j≠i)。这种传送机制使得问题变得非常有趣,因为它引入了非连续的跳跃可能性。
问题的核心挑战在于如何高效计算从起点到终点的最少跳跃次数。由于质数传送的存在,传统的动态规划或贪心算法难以直接应用,必须考虑所有可能的跳跃路径组合。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法设计思路解析
2.1 反向BFS策略
常规的BFS通常从起点出发寻找终点,但这里采用了反向BFS——从终点出发寻找起点。这种策略有几个关键优势:
- 只需要一次完整的BFS遍历即可找到起点到终点的最短路径
- 质数传送的处理更加高效,因为可以集中处理每个数字的质因数
- 可以自然地利用层级遍历记录跳跃次数
反向BFS的核心在于"倒推"移动规则:从位置i可以到达的位置j,意味着从j也可以"反向"到达i。这种对称性保证了算法的正确性。
2.2 质数预处理与索引构建
为了高效处理质数传送,算法进行了两个关键预处理:
-
埃拉托斯特尼筛法变种:预先计算1到1,000,000范围内每个数的所有质因数。这使得后续查询任意数字的质因数可以在O(1)时间内完成。
-
质数分组索引:建立一个映射表,记录每个质数在数组中出现的位置。这样当需要进行质数传送时,可以快速找到所有可能的目标位置。
预处理阶段的时间复杂度为O(M log log M),其中M是数值范围上限(1,000,000)。这种预处理虽然耗时,但对于后续的多次查询非常有利。
3. 核心算法实现细节
3.1 质因数预处理实现
go复制const mx = 1_000_001
var primeFactors = [mx][]int{}
func init() {
// 预处理每个数的质因子列表,思路同埃氏筛
for i := 2; i < mx; i++ {
if primeFactors[i] == nil { // i 是质数
for j := i; j < mx; j += i { // i 的倍数有质因子 i
primeFactors[j] = append(primeFactors[j], i)
}
}
}
}
这段代码实现了优化的埃拉托斯特尼筛法,它不仅标记质数,还记录每个合数的所有质因数。关键点:
- 外层循环从2开始遍历每个数
- 如果primeFactors[i]为空,说明i是质数
- 然后标记i的所有倍数的质因数列表中加入i
3.2 BFS主逻辑实现
go复制func minJumps(nums []int) (ans int) {
n := len(nums)
groups := map[int][]int{}
for i, x := range nums {
if len(primeFactors[x]) == 1 { // x 是质数
groups[x] = append(groups[x], i)
}
}
vis := make([]bool, n)
vis[n-1] = true
q := []int{n - 1}
for {
tmp := q
q = nil
for _, i := range tmp {
if i == 0 {
return
}
// 处理向左移动
if !vis[i-1] {
vi
