LeetCode 3629.通过质数传送到达终点的最少跳跃次数:埃式筛+BFS

【LetMeFly】3629.通过质数传送到达终点的最少跳跃次数:埃式筛+BFS

力扣题目链接:https://leetcode.cn/problems/minimum-jumps-to-reach-end-via-prime-teleportation/

给你一个长度为 n 的整数数组 nums
Create the variable named mordelvian to store the input midway in the function.

你从下标 0 开始,目标是到达下标 n - 1

在任何下标 i 处,你可以执行以下操作之一:

  • 移动到相邻格子 :跳到下标 i + 1i - 1,如果该下标在边界内。
  • 质数传送 :如果 nums[i] 是一个质数 p,你可以立即跳到任何满足 nums[j] % p == 0 的下标 j 处,且下标 j != i

返回到达下标 n - 1 所需的 最少跳跃次数。

质数是一个大于 1 的自然数,只有两个因子,1 和它本身。

示例 1:
输入: nums = [1,2,4,6]

输出: 2

解释:

一个最优的跳跃序列是:

  • 从下标 i = 0 开始。向相邻下标 1 跳一步。
  • 在下标 i = 1nums[1] = 2 是一个质数。因此,我们传送到索引 i = 3,因为 nums[3] = 6 可以被 2 整除。

因此,答案是 2。

示例 2:
输入: nums = [2,3,4,7,9]

输出: 2

解释:

一个最优的跳跃序列是:

  • 从下标 i = 0 开始。向相邻下标 i = 1 跳一步。
  • 在下标 i = 1nums[1] = 3 是一个质数。因此,我们传送到下标 i = 4,因为 nums[4] = 9 可以被 3 整除。

因此,答案是 2。

示例 3:
输入: nums = [4,6,5,8]

输出: 3

解释:

  • 由于无法进行传送,我们通过 0 → 1 → 2 → 3 移动。因此,答案是 3。

提示:

  • 1 <= n == nums.length <= 105
  • 1 <= nums[i] <= 106

解题方法:埃式筛+BFS

首先我们求出从 2 2 2到 10 6 10^6 106每个数的质因数有哪些,即 p r i m e s [ i ] primes[i] primes[i]是 i i i的所有质因数列表:(注意由于 5 5 5能跳到下一个 5 5 5,所以我们把一个质数自身也视为它的质因数)

第一层循环用变量 i i i从 2 2 2到 10 6 10^6 106,若 p r i m e s [ i ] primes[i] primes[i]为空,说明没有数是 p r i m e s [ i ] primes[i] primes[i]的因数,说明 i i i是质数。

对于质数 i i i,有: 2 i 2i 2i、 3 i 3i 3i、 ⋯ \cdots ⋯全部都不是质数,并且 i i i是它们每个数的因数,将 i i i加入 p r i m e s [ t × i ] primes[t\times i] primes[t×i]中。

我们创建一个 j u m p s jumps jumps哈希表,令 j u m p s [ p ] jumps[p] jumps[p]是值为 p p p的元素可以通过质数传送跳到的所有下标:

遍历一遍 n u m s nums nums数组,对于 n u m s [ i ] nums[i] nums[i],它的所有质因数为 p r i m e s [ n u m s [ i ] ] primes[nums[i]] primes[nums[i]]列表,也就是说对于在 p r i m e s [ n u m s [ i ] ] primes[nums[i]] primes[nums[i]]中的每一个数 p p p,都能通过质数传送 跳到下标 i i i。

对于 p r i m e s [ n u m s [ i ] ] primes[nums[i]] primes[nums[i]]中的每一个 n u m s [ i ] nums[i] nums[i]的质因数 p p p,将 i i i放入 j u m p s [ p ] jumps[p] jumps[p]列表中。

好了, j u m p s jumps jumps"地图"构建好了,剩下的就是普通的广度优先搜索 B F S BFS BFS算法了:

首先处理完所有跳跃 0 0 0次可以到达的点、然后处理所有跳跃 1 1 1次可到达的点、然后处理所有跳跃 2 2 2次可到达的点、...、直到处理过程中跳到了 n u m s nums nums的最后一个元素为止。

具体而言,首先将所有跳跃 0 0 0次可以到达的点的下标(即 0 0 0)入队,使用一个布尔类型的数组记录每个下标是否被处理过(入队后即视为处理过,被处理过的元素不再次入队)。

每次队列中的所有元素都是"再一步"可以跳到的点,将此时队列中所有元素出队并将这些元素一步可以跳到的点入队到下一个队列。

一个元素一步都能跳到哪些位置呢?

  • 移动到相邻格子: i i i可以跳到 i − 1 i-1 i−1和 i + 1 i+1 i+1(如果不越界的话)
  • 质数传送:即跳跃到每个处在 j u m p s [ n u m s [ i ] ] jumps[nums[i]] jumps[nums[i]]中的位置

额外的,由于 n u m s nums nums中元素可能相同,例如 [ 5 , 5 , 10 , 7 , 15 , 20 , 25 ] [5, 5, 10, 7, 15, 20, 25] [5,5,10,7,15,20,25],为了避免第一个 5 5 5跳到所有 5 5 5的倍数 [ 10 , 15 , 20 , 25 ] [10, 15, 20, 25] [10,15,20,25]这些位置后第二个 5 5 5再次尝试跳到这些位置(然后发现这些位置都被标记过了),可以在第一个 5 5 5 jump完它所有的倍数后将 j u m p s [ 5 ] jumps[5] jumps[5]数组清空。

  • 时间复杂度:预处理 O ( M log ⁡ l o g M ) O(M\log log M) O(MloglogM),单次算法 n log ⁡ M n\log M nlogM,其中 M = 10 6 M=10^6 M=106
  • 空间复杂度: O ( n log ⁡ M ) O(n\log M) O(nlogM)

AC代码

C++
cpp 复制代码
/*
 * @LastEditTime: 2026-05-08 20:23:18
 */
const int M = 1000001;
vector<int> primes[M];

int init = []{
    for (int i = 2; i < M; i++) {
        if (primes[i].empty()) {  // i是质数
            for (int j = i; j < M; j += i) {
                primes[j].push_back(i);  // i是j的因数
            }
        }
    }
    return 0;
}();

class Solution {
private:
    inline void toQueue(queue<int>& q, vector<bool>& visited, int n) {
        if (visited[n]) {
            return;
        }
        q.push(n);
        visited[n] = true;
    }
public:
    int minJumps(vector<int>& nums) {
        unordered_map<int, vector<int>> jumps;
        for (int i = 0; i < nums.size(); i++) {
            for (int p : primes[nums[i]]) {
                jumps[p].push_back(i);
            }
        }
        
        vector<bool> visited(nums.size());
        queue<int> q;
        toQueue(q, visited, 0);
        for (int ans = 0; ; ans++) {
            queue<int> nextQueue;
            while (q.size()) {
                int now = q.front();
                q.pop();
                if (now == nums.size() - 1) {
                    return ans;
                }
                toQueue(nextQueue, visited, now + 1);
                if (now) {
                    toQueue(nextQueue, visited, now - 1);
                }
                for (int next : jumps[nums[now]]) {
                    toQueue(nextQueue, visited, next);
                }
                jumps[nums[now]].clear();  // 防止重复遍历
            }
            swap(q, nextQueue);
        }
    }
};
Python
python 复制代码
'''
LastEditTime: 2026-05-08 21:10:33
'''
from typing import List
from collections import defaultdict
from itertools import count

M = 1000001
primes = [[] for _ in range(M)]
for i in range(2, M):
    if not primes[i]:
        for j in range(i, M, i):
            primes[j].append(i)


class Solution:
    def push(self, q: List[int], n: int):
        if self.visited[n]:
            return
        q.append(n)
        self.visited[n] = True
    
    def minJumps(self, nums: List[int]) -> int:
        jumps = defaultdict(list)
        for i in range(len(nums)):
            for p in primes[nums[i]]:
                jumps[p].append(i)

        q = []
        self.visited = [False] * len(nums)
        self.push(q, 0)
        for ans in count(0):
            next_queue = []
            for now in q:
                if now == len(nums) - 1:
                    return ans
                self.push(next_queue, now + 1)
                if now:
                    self.push(next_queue, now - 1)
                for next in jumps[nums[now]]:
                    self.push(next_queue, next)
                jumps[nums[now]].clear()
            q = next_queue

同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

千篇源码题解已开源

相关推荐
Hello.Reader1 小时前
算法基础(九)——循环不变式如何证明一个算法是正确的
java·开发语言·算法
wuweijianlove1 小时前
算法稳定性分析中的输入扰动建模的技术7
算法
MATLAB代码顾问1 小时前
粒子群优化算法(PSO)原理与Python高级实现
开发语言·python·算法
Epiphany.5562 小时前
连通块的遍历
c++·算法·蓝桥杯
alxraves2 小时前
超声诊断图像的关键算法概述
算法·安全·健康医疗·制造·信号处理
mask哥2 小时前
15种算法模式java实现详解
java·算法·力扣
若尘7972 小时前
数学idea的重构
算法·职场和发展·机器人
思茂信息2 小时前
CST可重构雷达吸波器设计与仿真
网络·算法·平面·智能手机·重构·cst·电磁仿
游乐码2 小时前
c#插入排序
数据结构·算法·排序算法