Medium

题目描述

给你一个长度为 n 的整数数组 nums。

你从索引 0 开始,目标是到达索引 n - 1。

从任何索引 i,你可以执行以下操作之一:

  • 相邻步骤:跳到索引 i + 1 或 i - 1,如果索引在边界内。
  • 质数传送:如果 nums[i] 是质数 p,你可以立即跳到任何索引 j != i,使得 nums[j] % p == 0。

返回到达索引 n - 1 所需的最小跳跃次数。

示例 1:

输入:nums = [1,2,4,6]
输出:2
解释:
一个最优的跳跃序列是:
- 从索引 i = 0 开始。相邻步骤到索引 1。
- 在索引 i = 1,nums[1] = 2 是质数。因此,我们传送到索引 i = 3,因为 nums[3] = 6 能被 2 整除。

因此答案是 2。

示例 2:

输入:nums = [2,3,4,7,9]
输出:2
解释:
一个最优的跳跃序列是:
- 从索引 i = 0 开始。相邻步骤到索引 i = 1。
- 在索引 i = 1,nums[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 <= 10^5
  • 1 <= nums[i] <= 10^6

解题思路

这道题可以用BFS求解最短路径问题。关键在于理解两种移动方式:相邻步骤和质数传送。

核心思路:

  1. 预处理质因数分解:对于每个数组元素,找到其所有质因数。可以使用筛法预处理所有质数,然后对每个数进行质因数分解。

  2. 构建质数到索引的映射:对每个质数p,记录所有满足nums[j] % p == 0的索引j。这样在BFS过程中,当我们在索引i且nums[i]是质数时,可以快速找到所有可传送的目标位置。

  3. BFS搜索

    • 从索引0开始BFS
    • 对于当前位置i,可以:
      • 相邻移动到i-1或i+1(如果在范围内)
      • 如果nums[i]包含质因数p,可以传送到所有nums[j] % p == 0的位置j
  4. 优化技巧:为避免重复访问,当我们通过某个质数p进行传送后,应该清空该质数对应的索引列表,确保每个质数的传送只使用一次。

这种方法的时间复杂度主要取决于质因数分解和BFS遍历,空间复杂度用于存储质数映射和BFS队列。

代码实现

class Solution {
public:
    int minJumps(vector<int>& nums) {
        int n = nums.size();
        if (n == 1) return 0;
        
        // 预计算所有质因数
        unordered_map<int, vector<int>> primeToIndices;
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            for (int p = 2; p * p <= num; p++) {
                if (num % p == 0) {
                    primeToIndices[p].push_back(i);
                    while (num % p == 0) num /= p;
                }
            }
            if (num > 1) {
                primeToIndices[num].push_back(i);
            }
        }
        
        vector<bool> visited(n, false);
        queue<int> q;
        q.push(0);
        visited[0] = true;
        int steps = 0;
        
        while (!q.empty()) {
            int size = q.size();
            for (int i = 0; i < size; i++) {
                int curr = q.front();
                q.pop();
                
                if (curr == n - 1) return steps;
                
                // 相邻步骤
                if (curr + 1 < n && !visited[curr + 1]) {
                    visited[curr + 1] = true;
                    q.push(curr + 1);
                }
                if (curr - 1 >= 0 && !visited[curr - 1]) {
                    visited[curr - 1] = true;
                    q.push(curr - 1);
                }
                
                // 质数传送
                int num = nums[curr];
                for (int p = 2; p * p <= num; p++) {
                    if (num % p == 0) {
                        for (int next : primeToIndices[p]) {
                            if (!visited[next]) {
                                visited[next] = true;
                                q.push(next);
                            }
                        }
                        primeToIndices[p].clear(); // 清空避免重复访问
                        while (num % p == 0) num /= p;
                    }
                }
                if (num > 1) {
                    for (int next : primeToIndices[num]) {
                        if (!visited[next]) {
                            visited[next] = true;
                            q.push(next);
                        }
                    }
                    primeToIndices[num].clear(); // 清空避免重复访问
                }
            }
            steps++;
        }
        
        return -1;
    }
};
class Solution:
    def minJumps(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 1:
            return 0
        
        # 预计算所有质因数到索引的映射
        prime_to_indices = defaultdict(list)
        for i in range(n):
            num = nums[i]
            p = 2
            while p * p <= num:
                if num % p == 0:
                    prime_to_indices[p].append(i)
                    while num % p == 0:
                        num //= p
                p += 1
            if num > 1:
                prime_to_indices[num].append(i)
        
        visited = [False] * n
        queue = deque([0])
        visited[0] = True
        steps = 0
        
        while queue:
            for _ in range(len(queue)):
                curr = queue.popleft()
                
                if curr == n - 1:
                    return steps
                
                # 相邻步骤
                for next_pos in [curr - 1, curr + 1]:
                    if 0 <= next_pos < n and not visited[next_pos]:
                        visited[next_pos] = True
                        queue.append(next_pos)
                
                # 质数传送
                num = nums[curr]
                p = 2
                while p * p <= num:
                    if num % p == 0:
                        for next_pos in prime_to_indices[p]:
                            if not visited[next_pos]:
                                visited[next_pos] = True
                                queue.append(next_pos)
                        prime_to_indices[p].clear()  # 清空避免重复访问
                        while num % p == 0:
                            num //= p
                    p += 1
                if num > 1:
                    for next_pos in prime_to_indices[num]:
                        if not visited[next_pos]:
                            visited[next_pos] = True
                            queue.append(next_pos)
                    prime_to_indices[num].clear()  # 清空避免重复访问
            
            steps += 1
        
        return -1
public class Solution {
    public int MinJumps(int[] nums) {
        int n = nums.Length;
        if (n == 1) return 0;
        
        // 预计算所有质因数到索引的映射
        var primeToIndices = new Dictionary<int, List<int>>();
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            for (int p = 2; p * p <= num; p++) {
                if (num % p == 0) {
                    if (!primeToIndices.ContainsKey(p)) {
                        primeToIndices[p] = new List<int>();
                    }
                    primeToIndices[p].Add(i);
                    while (num % p == 0) num /= p;
                }
            }
            if (num > 1) {
                if (!primeToIndices.ContainsKey(num)) {
                    primeToIndices[num] = new List<int>();
                }
                primeToIndices[num].Add(i);
            }
        }
        
        bool[] visited = new bool[n];
        var queue = new Queue<int>();
        queue.Enqueue(0);
        visited[0] = true;
        int steps = 0;
        
        while (queue.Count > 0) {
            int size = queue.Count;
            for (int i = 0; i < size; i++) {
                int curr = queue.Dequeue();
                
                if (curr == n - 1) return steps;
                
                // 相邻步骤
                if (curr + 1 < n && !visited[curr + 1]) {
                    visited[curr + 1] = true;
                    queue.Enqueue(curr + 1);
                }
                if (curr - 1 >= 0 && !visited[curr - 1]) {
                    visited[curr - 1] = true;
                    queue.Enqueue(curr - 1);
                }
                
                // 质数传送
                int num = nums[curr];
                for (int p = 2; p * p <= num; p++) {
                    if (num % p == 0) {
                        if (primeToIndices.ContainsKey(p)) {
                            foreach (int next in primeToIndices[p]) {
                                if (!visited[next]) {
                                    visited[next] = true;
                                    queue.Enqueue(next);
                                }
                            }
                            primeToIndices[p].Clear(); // 清空避免重复访问
                        }
                        while (num % p == 0) num /= p;
                    }
                }
                if (num > 1) {
                    if (primeToIndices.ContainsKey(num)) {
                        foreach (int next in primeToIndices[num]) {
                            if (!visited[next]) {
                                visited[next] = true;
                                queue.Enqueue(next);
                            }
                        }
                        primeToIndices[num].Clear(); // 清空避免重复访问
                    }
                }
            }
            steps++;
        }
        
        return -1;
    }
}
var minJumps = function(nums) {
    const n = nums.length;
    if (n === 1) return 0;
    
    const isPrime = (num) => {
        if (num < 2) return false;
        if (num === 2) return true;
        if (num % 2 === 0) return false;
        for (let i = 3; i * i <= num; i += 2) {
            if (num % i === 0) return false;
        }
        return true;
    };
    
    const queue = [0];
    const visited = new Set([0]);
    let jumps = 0;
    
    while (queue.length > 0) {
        const size = queue.length;
        
        for (let i = 0; i < size; i++) {
            const curr = queue.shift();
            
            if (curr === n - 1) return jumps;
            
            // Adjacent steps
            for (const next of [curr - 1, curr + 1]) {
                if (next >= 0 && next < n && !visited.has(next)) {
                    visited.add(next);
                    queue.push(next);
                }
            }
            
            // Prime teleportation
            if (isPrime(nums[curr])) {
                const prime = nums[curr];
                for (let j = 0; j < n; j++) {
                    if (j !== curr && nums[j] % prime === 0 && !visited.has(j)) {
                        visited.add(j);
                        queue.push(j);
                    }
                }
            }
        }
        
        jumps++;
    }
    
    return -1;
};

复杂度分析

复杂度类型分析
时间复杂度O(n × √max(nums) + V + E),其中n是数组长度,V是顶点数(索引数),E是边数(相邻边+质数传送边)
空间复杂度O(n + P),其中P是不同质因数的数量,用于存储质数到索