Medium

题目描述

给定两个由相同位数组成的整数 n 和 m。

你可以执行以下操作任意次数:

  • 选择 n 中任意一个不是 9 的数字,将其增加 1。
  • 选择 n 中任意一个不是 0 的数字,将其减少 1。

整数 n 在任何时候都不能是质数,包括其原始值和每次操作后的值。

变换的成本是 n 在所有执行的操作过程中所取的所有值的总和。

返回将 n 转换为 m 的最小成本。如果不可能,返回 -1。

示例 1:

输入:n = 10, m = 12
输出:85
解释:
我们执行以下操作:
- 增加第一个数字,现在 n = 20。
- 增加第二个数字,现在 n = 21。
- 增加第二个数字,现在 n = 22。
- 减少第一个数字,现在 n = 12。

示例 2:

输入:n = 4, m = 8
输出:-1
解释:
无法使 n 等于 m。

示例 3:

输入:n = 6, m = 2
输出:-1
解释:
由于 2 已经是质数,我们无法使 n 等于 m。

约束条件:

  • 1 <= n, m < 10^4
  • n 和 m 由相同的位数组成。

解题思路

这是一个经典的最短路径问题。我们需要将其建模为图论问题:

核心思路:

  1. 将每个非质数看作图中的节点
  2. 如果两个数可以通过一次操作相互转换,则在它们之间建立边
  3. 边的权重为目标数字的值
  4. 使用 Dijkstra 算法求从 n 到 m 的最短路径

具体步骤:

  1. 首先预处理所有可能的质数(使用埃拉托斯特尼筛法)
  2. 检查起点 n 和终点 m 是否为质数,如果是则直接返回 -1
  3. 对于每个非质数,生成其所有可能的邻居(通过增减各位数字)
  4. 使用优先队列实现 Dijkstra 算法,寻找最短路径
  5. 路径的成本是经过的所有节点值的总和

关键细节:

  • 数字操作时需要保证不超出位数限制
  • 每次变换后的数字都不能是质数
  • 成本计算包含起始值 n

这种方法的时间复杂度主要取决于图的大小和 Dijkstra 算法的执行。

代码实现

class Solution {
public:
    int minOperations(int n, int m) {
        if (n == m) return n;
        
        // 预处理质数
        vector<bool> isPrime(10001, true);
        isPrime[0] = isPrime[1] = false;
        for (int i = 2; i * i <= 10000; i++) {
            if (isPrime[i]) {
                for (int j = i * i; j <= 10000; j += i) {
                    isPrime[j] = false;
                }
            }
        }
        
        if (isPrime[n] || isPrime[m]) return -1;
        
        // Dijkstra算法
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
        vector<int> dist(10001, INT_MAX);
        
        pq.push({n, n});
        dist[n] = n;
        
        while (!pq.empty()) {
            auto [cost, curr] = pq.top();
            pq.pop();
            
            if (curr == m) return cost;
            if (cost > dist[curr]) continue;
            
            string s = to_string(curr);
            
            // 尝试增加每一位
            for (int i = 0; i < s.length(); i++) {
                if (s[i] < '9') {
                    s[i]++;
                    int next = stoi(s);
                    if (!isPrime[next] && cost + next < dist[next]) {
                        dist[next] = cost + next;
                        pq.push({cost + next, next});
                    }
                    s[i]--;
                }
            }
            
            // 尝试减少每一位
            for (int i = 0; i < s.length(); i++) {
                if (s[i] > '0' && (i > 0 || s[i] > '1')) {
                    s[i]--;
                    int next = stoi(s);
                    if (!isPrime[next] && cost + next < dist[next]) {
                        dist[next] = cost + next;
                        pq.push({cost + next, next});
                    }
                    s[i]++;
                }
            }
        }
        
        return -1;
    }
};
class Solution:
    def minOperations(self, n: int, m: int) -> int:
        if n == m:
            return n
        
        # 预处理质数
        is_prime = [True] * 10001
        is_prime[0] = is_prime[1] = False
        for i in range(2, int(10000**0.5) + 1):
            if is_prime[i]:
                for j in range(i * i, 10001, i):
                    is_prime[j] = False
        
        if is_prime[n] or is_prime[m]:
            return -1
        
        # Dijkstra算法
        import heapq
        pq = [(n, n)]
        dist = [float('inf')] * 10001
        dist[n] = n
        
        while pq:
            cost, curr = heapq.heappop(pq)
            
            if curr == m:
                return cost
            if cost > dist[curr]:
                continue
            
            s = list(str(curr))
            
            # 尝试增加每一位
            for i in range(len(s)):
                if s[i] < '9':
                    s[i] = str(int(s[i]) + 1)
                    next_num = int(''.join(s))
                    if not is_prime[next_num] and cost + next_num < dist[next_num]:
                        dist[next_num] = cost + next_num
                        heapq.heappush(pq, (cost + next_num, next_num))
                    s[i] = str(int(s[i]) - 1)
            
            # 尝试减少每一位
            for i in range(len(s)):
                if s[i] > '0' and (i > 0 or s[i] > '1'):
                    s[i] = str(int(s[i]) - 1)
                    next_num = int(''.join(s))
                    if not is_prime[next_num] and cost + next_num < dist[next_num]:
                        dist[next_num] = cost + next_num
                        heapq.heappush(pq, (cost + next_num, next_num))
                    s[i] = str(int(s[i]) + 1)
        
        return -1
public class Solution {
    public int MinOperations(int n, int m) {
        if (n == m) return n;
        
        // 预处理质数
        bool[] isPrime = new bool[10001];
        Array.Fill(isPrime, true);
        isPrime[0] = isPrime[1] = false;
        for (int i = 2; i * i <= 10000; i++) {
            if (isPrime[i]) {
                for (int j = i * i; j <= 10000; j += i) {
                    isPrime[j] = false;
                }
            }
        }
        
        if (isPrime[n] || isPrime[m]) return -1;
        
        // Dijkstra算法
        var pq = new PriorityQueue<(int cost, int num), int>();
        int[] dist = new int[10001];
        Array.Fill(dist, int.MaxValue);
        
        pq.Enqueue((n, n), n);
        dist[n] = n;
        
        while (pq.Count > 0) {
            var (cost, curr) = pq.Dequeue();
            
            if (curr == m) return cost;
            if (cost > dist[curr]) continue;
            
            char[] s = curr.ToString().ToCharArray();
            
            // 尝试增加每一位
            for (int i = 0; i < s.Length; i++) {
                if (s[i] < '9') {
                    s[i]++;
                    int next = int.Parse(new string(s));
                    if (!isPrime[next] && cost + next < dist[next]) {
                        dist[next] = cost + next;
                        pq.Enqueue((cost + next, next), cost + next);
                    }
                    s[i]--;
                }
            }
            
            // 尝试减少每一位
            for (int i = 0; i < s.Length; i++) {
                if (s[i] > '0' && (i > 0 || s[i] > '1')) {
                    s[i]--;
                    int next = int.Parse(new string(s));
                    if (!isPrime[next] && cost + next < dist[next]) {
                        dist[next] = cost + next;
                        pq.Enqueue((cost + next, next), cost + next);
                    }
                    s[i]++;
                }
            }
        }
        
        return -1;
    }
}
var minOperations = function(n, m) {
    function 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;
    }
    
    if (isPrime(n) || isPrime(m)) return -1;
    if (n === m) return n;
    
    const visited = new Set();
    const pq = [[n, n]]; // [current_number, cost]
    
    while (pq.length > 0) {
        pq.sort((a, b) => a[1] - b[1]);
        const [curr, cost] = pq.shift();
        
        if (curr === m) return cost;
        if (visited.has(curr)) continue;
        visited.add(curr);
        
        const digits = curr.toString().split('').map(Number);
        
        for (let i = 0; i < digits.length; i++) {
            // Increase digit
            if (digits[i] < 9) {
                const newDigits = [...digits];
                newDigits[i]++;
                const newNum = parseInt(newDigits.join(''));
                if (!isPrime(newNum) && !visited.has(newNum)) {
                    pq.push([newNum, cost + newNum]);
                }
            }
            
            // Decrease digit
            if (digits[i] > 0 && !(i === 0 && digits[i] === 1 && digits.length > 1)) {
                const newDigits = [...digits];
                newDigits[i]--;
                const newNum = parseInt(newDigits.join(''));
                if (!isPrime(newNum) && !visited.has(newNum)) {
                    pq.push([newNum, cost + newNum]);
                }
            }
        }
    }
    
    return -1;
};

复杂度分析

复杂度分析
时间复杂度O(V log V + E),其中 V 是所有可能的非质数个数(约 8000 个),E 是边的数量。每个数字最多有 O(d) 个邻居(d 为位数),所以 E ≈ O(V × d)。Dijkstra 算法的复杂度为 O((V + E) log V)
空间复杂度O(V),用于存储距离数组、质数标记数组和优先队列