Facebook Pixel

3377. Digit Operations to Make Two Integers Equal


Problem Description

You are given two integers n and m that consist of the same number of digits. You can perform the following operations any number of times:

  • Choose any digit from n that is not 9 and increase it by 1.
  • Choose any digit from n that is not 0 and decrease it by 1.

The integer n must not be a prime number at any point, including its original value and after each operation. The cost of a transformation is the sum of all values that n takes throughout the operations performed. Return the minimum cost to transform n into m. If it is impossible, return -1.

Intuition

The goal is to transform the integer n into m by adjusting its digits while ensuring that n never becomes a prime number during the process. Moreover, the transformation should be done in such a way that the total cost, defined as the sum of all values that n takes throughout the transformation, is minimized.

Here's how we can approach the problem:

  1. Verify Non-Primality: Start by checking if either n or m is prime initially. If either is prime, return -1 immediately because it's impossible to start or end at a prime number.

  2. Precompute Prime Numbers: Use the Sieve of Eratosthenes to precompute all prime numbers up to a large number (here, 100,000) to efficiently check primality as the digits of n are altered.

  3. Breadth-First Search (BFS): Use a priority queue (min-heap) to explore all possible transformations of n. Each state in the queue consists of a tuple containing the current cost and the current value of n.

  4. Transform and Explore: For each value of n, consider all possible digit transformations that either increase or decrease a digit within its range. Ensure that these transformations do not result in a prime number. Add these new states to the queue with the updated cost.

  5. End Condition: The process continues until n is transformed into m. To achieve the minimum cost transformation, always prioritize states with lower cumulative costs due to the nature of the min-heap.

  6. Impossible Transformation: If there is no path to transform n into m without turning n prime, return -1.

This approach effectively finds the least costly transformation by verifying non-primal transitions during digit changes, using a systematic BFS strategy aided by a precomputed sieve for primality checks.

Learn more about Graph, Math, Shortest Path and Heap (Priority Queue) patterns.

Solution Approach

The solution implements an algorithm to transform the number n into m with the minimum cost under the given constraints. Here's a breakdown of how the implementation works:

  1. Prime Number Precomputation: The solution begins with the run_sieve method, which uses the Sieve of Eratosthenes to precompute a list of prime numbers up to 100,000. This list, stored as self.sieve, allows for rapid primality checks.

    def run_sieve(self):
        self.sieve = [True] * 100000
        self.sieve[0], self.sieve[1] = False, False
        for i in range(2, 100000):
            if self.sieve[i]:
                for j in range(2 * i, 100000, i):
                    self.sieve[j] = False
  2. Breadth-First Search Using a Priority Queue: The minOperations method initiates the search with a priority queue (pq). This queue is used to explore transformations of n, prioritizing paths with the lowest cumulative cost due to the use of a min-heap.

  3. State Exploration: The priority queue is initialized with the starting number n, and its initial cost. The solve method contains the core logic that performs BFS to explore valid transformations:

    def solve(self, n, m):
        pq = []
        heapq.heappush(pq, (n, n))
        visited = set()
    
        while pq:
            sum_, cur = heapq.heappop(pq)
    
            if cur in visited:
                continue
            visited.add(cur)
    
            if cur == m:
                return sum_
          
            # Transform each digit
            s = list(str(cur))
            for i in range(len(s)):
                c = s[i]
    
                # Increase digit if it's not 9
                if s[i] < '9':
                    s[i] = chr(ord(s[i]) + 1)
                    next_ = int(''.join(s))
                    if not self.sieve[next_] and next_ not in visited:
                        heapq.heappush(pq, (sum_ + next_, next_))
                    s[i] = c  # Revert
    
                # Decrease digit if it's not 0
                if s[i] > '0' and not (i == 0 and s[i] == '1'):
                    s[i] = chr(ord(s[i]) - 1)
                    next_ = int(''.join(s))
                    if not self.sieve[next_] and next_ not in visited:
                        heapq.heappush(pq, (sum_ + next_, next_))
                    s[i] = c  # Revert
    
        return -1
  4. Digit Modification Rules: For each digit of the current number, the solution considers increasing it if it's not 9 and decreasing it if it's not 0, ensuring the number does not turn prime after each modification.

  5. Termination and Result: The search concludes when n transforms into m. If successful transformations exist, the total cost is returned. If no valid transformation is possible, -1 is returned.

This approach leverages efficient primality checks and exploration strategies to achieve an optimal solution path while adhering to the constraints.

Ready to land your dream job?

Unlock your dream job with a 2-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

To illustrate the solution approach, let's consider an example where n = 131 and m = 132.

  1. Initial Check for Non-Primality:
    • First, we check if n or m is a prime number. Both 131 and 132 are checked against our precomputed sieve. It turns out 131 is prime, so initial conditions make the transformation impossible.
    • Consequently, we return -1 because the problem specifies that neither the original value nor any intermediate transformation of n can be prime.

Consider an example where transformations are possible:

  • Let n = 120 and m = 123.
  1. Initialize the Priority Queue:

    • Start with a priority queue initialized with the value 120 and its current cost (which is itself initially), i.e., (120, 120).
  2. State Exploration and Transitions:

    • Current n = 120. Check possible transformations:
      • Increase the rightmost digit 0 to 1. Gives 121, which is not prime and less than 123.
      • Push (241, 121) into the queue (241 is the cumulative cost 120 + 121).
  3. Subsequent Steps:

    • Explore 121:
      • Increase 1 at the end to 2. Gives 122.
      • Ensure 122 isn't prime and push (363, 122) into the queue (363 is 241 + 122).
    • Explore 122:
      • Increase 2 at the end to 3. Results in 123.
      • Verify 123 isn't prime. Push (486, 123) into the queue (486 is 363 + 123).
  4. Reach the Target m:

    • We finally pop 123 from the queue, reaching our target number. The total minimal cost accumulated is 486.

This approach guarantees that we neither start nor transition into a prime, finding the sequence and corresponding cost that transforms n into m with minimal change along a non-prime path.

Solution Implementation

1import heapq
2
3class Solution:
4    def __init__(self):
5        # Initialize the sieve list that will be used for marking primes
6        self.sieve = []
7
8    def run_sieve(self):
9        # Create a list to mark non-prime numbers using the Sieve of Eratosthenes method
10        self.sieve = [True] * 100000
11        self.sieve[0], self.sieve[1] = False, False  # 0 and 1 are not prime numbers
12
13        # Iterate over each number to mark its multiples as non-prime
14        for i in range(2, 100000):
15            if self.sieve[i]:
16                # Mark all multiples of i as non-prime
17                for j in range(2 * i, 100000, i):
18                    self.sieve[j] = False
19
20    def solve(self, n, m):
21        # Priority queue for BFS
22        priority_queue = []
23      
24        # Start with the initial number n
25        heapq.heappush(priority_queue, (n, n))
26      
27        # Track visited numbers
28        visited = set()
29
30        while priority_queue:
31            # Pop the minimum sum value from the priority queue
32            sum_, current = heapq.heappop(priority_queue)
33
34            # Check if this number has already been visited
35            if current in visited:
36                continue
37            visited.add(current)
38
39            # Check if we have reached the target number m
40            if current == m:
41                return sum_
42
43            # Convert the current number to a list of characters for manipulation
44            str_num = list(str(current))
45            for i in range(len(str_num)):
46                original_digit = str_num[i]
47
48                # Try increasing the current digit
49                if str_num[i] < '9':
50                    # Increase by one
51                    str_num[i] = chr(ord(str_num[i]) + 1)
52                    next_number = int(''.join(str_num))
53                  
54                    # If the next number is not prime and not visited, add it to the priority queue
55                    if not self.sieve[next_number] and next_number not in visited:
56                        heapq.heappush(priority_queue, (sum_ + next_number, next_number))
57                  
58                    # Revert back to the original digit
59                    str_num[i] = original_digit
60
61                # Try decreasing the current digit
62                if str_num[i] > '0' and not (i == 0 and str_num[i] == '1'):
63                    # Decrease by one
64                    str_num[i] = chr(ord(str_num[i]) - 1)
65                    next_number = int(''.join(str_num))
66                  
67                    # If the next number is not prime and not visited, add it to the priority queue
68                    if not self.sieve[next_number] and next_number not in visited:
69                        heapq.heappush(priority_queue, (sum_ + next_number, next_number))
70                  
71                    # Revert back to the original digit
72                    str_num[i] = original_digit
73
74        # Return -1 if no transformation is possible from n to m
75        return -1
76
77    def minOperations(self, n, m):
78        # Generate the sieve for prime numbers up to 100,000
79        self.run_sieve()
80      
81        # If either input number is prime, return -1 as operations can't start from or reach a prime
82        if self.sieve[n] or self.sieve[m]:
83            return -1
84      
85        # Use a BFS-based solution to find the minimum operations from n to m
86        return self.solve(n, m)
87
1import java.util.*;
2
3class Solution {
4    private boolean[] sieve;
5  
6    // Method to initialize the sieve of Eratosthenes
7    private void runSieve() {
8        sieve = new boolean[100000];
9      
10        // Assume all numbers are prime initially
11        Arrays.fill(sieve, true);
12      
13        // 0 and 1 are not prime numbers
14        sieve[0] = false;
15        sieve[1] = false;
16      
17        // Sieve of Eratosthenes algorithm to mark non-prime numbers
18        for (int i = 2; i < 100000; i++) {
19            if (sieve[i]) {
20                // Mark all multiples of i as non-prime
21                for (int j = 2 * i; j < 100000; j += i) {
22                    sieve[j] = false;
23                }
24            }
25        }
26    }
27  
28    // Method to solve the problem of transforming n to m with minimum operations
29    private int solve(int n, int m) {
30        // Priority queue to store pairs of sum and current number, sorted by sum
31        PriorityQueue<int[]> priorityQueue = new PriorityQueue<>(Comparator.comparingInt(a -> a[0]));
32      
33        // Add the initial number with its sum
34        priorityQueue.add(new int[] {n, n});
35      
36        // Set to keep track of visited numbers
37        Set<Integer> visited = new HashSet<>();
38      
39        while (!priorityQueue.isEmpty()) {
40            // Retrieve the top element in the priority queue
41            int[] top = priorityQueue.poll();
42            int sum = top[0], current = top[1];
43          
44            // Skip the number if it has been visited
45            if (visited.contains(current)) {
46                continue;
47            }
48          
49            // Mark the current number as visited
50            visited.add(current);
51
52            // If we have reached the target number, return the sum
53            if (current == m) {
54                return sum;
55            }
56
57            char[] digits = String.valueOf(current).toCharArray();
58          
59            // Iterate through each digit to attempt a change
60            for (int i = 0; i < digits.length; i++) {
61                char original = digits[i];
62
63                // Try increasing the digit if possible
64                if (digits[i] < '9') {
65                    digits[i] = (char) (digits[i] + 1);
66                    int nextNumber = Integer.parseInt(new String(digits));
67                    if (!sieve[nextNumber] && !visited.contains(nextNumber)) {
68                        priorityQueue.add(new int[] {sum + nextNumber, nextNumber});
69                    }
70                    // Revert the digit change
71                    digits[i] = original;
72                }
73
74                // Try decreasing the digit if possible
75                if (digits[i] > '0' && !(i == 0 && digits[i] == '1')) {
76                    digits[i] = (char) (digits[i] - 1);
77                    int nextNumber = Integer.parseInt(new String(digits));
78                    if (!sieve[nextNumber] && !visited.contains(nextNumber)) {
79                        priorityQueue.add(new int[] {sum + nextNumber, nextNumber});
80                    }
81                    // Revert the digit change
82                    digits[i] = original;
83                }
84            }
85        }
86      
87        // Return -1 if there is no possible way to reach m from n
88        return -1;
89    }
90  
91    // Public method to calculate the minimum operations
92    public int minOperations(int n, int m) {
93        runSieve();
94      
95        // If either n or m is a prime number, return -1
96        if (sieve[n] || sieve[m]) {
97            return -1;
98        }
99      
100        return solve(n, m);
101    }
102}
103
1#include <vector>
2#include <queue>
3#include <unordered_set>
4#include <string>
5#include <utility>
6
7using namespace std;
8
9class Solution {
10private:
11    vector<bool> sieve; // Sieve of Eratosthenes container to identify prime numbers.
12
13    // Method to execute the Sieve of Eratosthenes algorithm.
14    void runSieve() {
15        // Initialize sieve vector with true, indicating potential prime status.
16        sieve.resize(100000, true);
17        // 0 and 1 are not prime numbers.
18        sieve[0] = false;
19        sieve[1] = false;
20      
21        // Begin marking non-prime numbers.
22        for (int i = 2; i < 100000; ++i) {
23            if (sieve[i]) { // If i is a prime,
24                // Mark all multiples of i as non-prime.
25                for (int j = 2 * i; j < 100000; j += i) {
26                    sieve[j] = false;
27                }
28            }
29        }
30    }
31
32    // Function to solve the problem using a priority queue for minimum cost pathfinding.
33    int solve(int n, int m) {