Facebook Pixel

2059. Minimum Operations to Convert Number

Problem Description

You have an integer array nums with distinct numbers, a starting integer start, and a target integer goal. Your task is to transform the value x (initially set to start) into goal using the minimum number of operations.

For each operation, you can choose any number from the nums array and apply one of three operations to the current value of x:

  • Addition: x + nums[i]
  • Subtraction: x - nums[i]
  • Bitwise XOR: x ^ nums[i]

The key constraints are:

  • You can only perform operations when x is within the range [0, 1000]
  • Each number in nums can be used multiple times in any order
  • Operations that result in x going outside the [0, 1000] range are allowed, but once x is outside this range, no further operations can be performed
  • The goal value itself can be outside the [0, 1000] range

The solution uses a breadth-first search (BFS) approach to find the shortest path from start to goal. It explores all possible states by applying the three operations with each number in nums. The BFS queue tracks the current value and the number of steps taken. A visited array prevents revisiting states within the valid range [0, 1000]. The search continues until either the goal is reached (returning the minimum steps) or all possibilities are exhausted (returning -1 if impossible).

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: This problem can be modeled as a graph where each valid state (value of x in range [0, 1000]) is a node, and edges represent transitions using the three operations with numbers from nums.

Is it a tree?

  • No: The graph is not a tree because multiple paths can lead to the same state (e.g., different operations or different orders can produce the same value of x).

Is the problem related to directed acyclic graphs?

  • No: The graph can have cycles since we can potentially return to previous states through different operations.

Is the problem related to shortest paths?

  • Yes: We need to find the minimum number of operations (shortest path) to transform start to goal.

Is the graph Weighted?

  • No: Each operation has the same cost (1 step), making this an unweighted graph problem.

BFS

  • Yes: For unweighted shortest path problems, BFS is the optimal choice.

Conclusion: The flowchart suggests using BFS (Breadth-First Search) for finding the minimum number of operations. BFS is perfect here because:

  1. We're exploring states level by level (each level represents one more operation)
  2. The first time we reach the goal, we've found the shortest path
  3. All edges have equal weight (each operation counts as 1 step)
  4. We need to track visited states to avoid redundant exploration
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is to think of this problem as finding the shortest path in a state space. Each number within the range [0, 1000] represents a possible state, and we want to find the minimum steps to go from the start state to the goal state.

Why BFS? Consider what happens at each step: from any current value x, we can branch out to multiple new values by applying our three operations (+, -, ^) with each number in nums. This creates a tree-like exploration pattern where:

  • Level 0: just the start value
  • Level 1: all values reachable in 1 operation from start
  • Level 2: all values reachable in 2 operations from start
  • And so on...

Since BFS explores level by level, the first time we encounter the goal, we've found it using the minimum number of operations. This is guaranteed because we explore all possibilities at distance k before exploring any at distance k+1.

The constraint that operations can only be performed when x is in [0, 1000] simplifies our problem significantly. We only need to track visited states within this range, keeping our memory usage bounded. However, the goal itself can be outside this range - we just need to reach it in one final operation from a valid state.

Think of it like navigating through stepping stones (the values in [0, 1000]) to reach a destination (goal). Some destinations might be on the stones themselves, while others require one final leap from the last stone. We want to find the minimum number of hops needed, which is exactly what BFS gives us in an unweighted graph.

Learn more about Breadth-First Search patterns.

Solution Approach

The implementation uses BFS with a queue to systematically explore all reachable states. Here's how the solution works:

Data Structures:

  • A deque (double-ended queue) to store (current_value, steps_taken) pairs for BFS traversal
  • A boolean array vis of size 1001 to track visited states within the valid range [0, 1000]

Algorithm Steps:

  1. Initialize the BFS: Start with a queue containing (start, 0), where 0 represents zero steps taken initially.

  2. Define Operations: The solution elegantly defines three lambda functions for the operations:

    • op1: Addition (x + y)
    • op2: Subtraction (x - y)
    • op3: Bitwise XOR (x ^ y)
  3. BFS Exploration: While the queue is not empty:

    • Dequeue the current state (x, step)
    • For each number num in nums:
      • Apply each of the three operations to get a new value nx = op(x, num)
      • Check for goal: If nx == goal, immediately return step + 1 (we found the shortest path)
      • Check validity and visit status: If nx is within [0, 1000] and hasn't been visited:
        • Add (nx, step + 1) to the queue for further exploration
        • Mark nx as visited in the vis array
  4. Return -1: If the queue becomes empty without finding the goal, it's unreachable.

Key Optimizations:

  • The visited array prevents revisiting the same state, avoiding infinite loops and redundant computations
  • Early termination when goal is found ensures we get the minimum steps
  • The solution cleverly handles the case where goal is outside [0, 1000] by checking for it immediately after each operation, even if the result can't be used for further operations

Time Complexity: O(n * m) where n is the range size (1001) and m is the length of nums. Each state can be visited once, and for each state, we try all numbers with all operations.

Space Complexity: O(n) for the visited array and queue, where n is the range size (1001).

Ready to land your dream job?

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

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Example: nums = [2, 3], start = 5, goal = 11

We'll use BFS to find the minimum steps from 5 to 11.

Step 0 (Initial State):

  • Queue: [(5, 0)]
  • Visited: [5]

Step 1 (Process x=5, steps=0): From x=5, we try all operations with each number:

  • With num=2:
    • 5 + 2 = 7 β†’ Add (7, 1) to queue
    • 5 - 2 = 3 β†’ Add (3, 1) to queue
    • 5 ^ 2 = 7 (already added)
  • With num=3:
    • 5 + 3 = 8 β†’ Add (8, 1) to queue
    • 5 - 3 = 2 β†’ Add (2, 1) to queue
    • 5 ^ 3 = 6 β†’ Add (6, 1) to queue

Queue after Step 1: [(7,1), (3,1), (8,1), (2,1), (6,1)] Visited: [5, 7, 3, 8, 2, 6]

Step 2 (Process x=7, steps=1): From x=7, we try all operations:

  • With num=2:
    • 7 + 2 = 9 β†’ Add (9, 2) to queue
    • 7 - 2 = 5 (already visited, skip)
    • 7 ^ 2 = 5 (already visited, skip)
  • With num=3:
    • 7 + 3 = 10 β†’ Add (10, 2) to queue
    • 7 - 3 = 4 β†’ Add (4, 2) to queue
    • 7 ^ 3 = 4 (already added)

Step 3 (Process x=3, steps=1): From x=3, we continue exploring...

  • With num=2:
    • 3 + 2 = 5 (already visited)
    • 3 - 2 = 1 β†’ Add (1, 2) to queue
    • 3 ^ 2 = 1 (already added)
  • With num=3:
    • 3 + 3 = 6 (already visited)
    • 3 - 3 = 0 β†’ Add (0, 2) to queue
    • 3 ^ 3 = 0 (already added)

Step 4 (Process x=8, steps=1): From x=8, we check all operations:

  • With num=2:
    • 8 + 2 = 10 (already in queue)
    • 8 - 2 = 6 (already visited)
    • 8 ^ 2 = 10 (already in queue)
  • With num=3:
    • 8 + 3 = 11 β†’ This equals our goal! Return steps + 1 = 2

Answer: 2 steps

The shortest path is: 5 β†’ 8 (using 5+3) β†’ 11 (using 8+3)

This example demonstrates how BFS explores states level by level, ensuring that when we first reach the goal (11), we've found it using the minimum number of operations (2 steps).

Solution Implementation

1from collections import deque
2from typing import List
3
4class Solution:
5    def minimumOperations(self, nums: List[int], start: int, goal: int) -> int:
6        """
7        Find the minimum number of operations to transform start value to goal value.
8        Each operation applies one of three operations (add, subtract, XOR) with any number from nums.
9      
10        Args:
11            nums: List of integers to use in operations
12            start: Starting value
13            goal: Target value to reach
14          
15        Returns:
16            Minimum number of operations needed, or -1 if impossible
17        """
18        # Define the three possible operations
19        add_operation = lambda x, y: x + y
20        subtract_operation = lambda x, y: x - y
21        xor_operation = lambda x, y: x ^ y
22      
23        # Store all operations in a list for iteration
24        operations = [add_operation, subtract_operation, xor_operation]
25      
26        # Track visited values within the valid range [0, 1000]
27        # This prevents revisiting the same intermediate value
28        visited = [False] * 1001
29      
30        # BFS queue: each element is (current_value, steps_taken)
31        queue = deque([(start, 0)])
32      
33        # Perform BFS to find shortest path to goal
34        while queue:
35            current_value, steps = queue.popleft()
36          
37            # Try each number in nums
38            for num in nums:
39                # Try each operation (add, subtract, XOR)
40                for operation in operations:
41                    # Calculate the next value after applying operation
42                    next_value = operation(current_value, num)
43                  
44                    # Check if we've reached the goal
45                    if next_value == goal:
46                        return steps + 1
47                  
48                    # If the next value is within valid range and not visited
49                    # add it to the queue for further exploration
50                    if 0 <= next_value <= 1000 and not visited[next_value]:
51                        queue.append((next_value, steps + 1))
52                        visited[next_value] = True
53      
54        # If we exhaust all possibilities without reaching goal, return -1
55        return -1
56
1class Solution {
2    public int minimumOperations(int[] nums, int start, int goal) {
3        // Define the three operations as lambda functions
4        IntBinaryOperator addition = (x, y) -> x + y;
5        IntBinaryOperator subtraction = (x, y) -> x - y;
6        IntBinaryOperator xor = (x, y) -> x ^ y;
7      
8        // Array of operations to iterate through
9        IntBinaryOperator[] operations = {addition, subtraction, xor};
10      
11        // Visited array to track numbers we've already processed (range 0-1000)
12        boolean[] visited = new boolean[1001];
13      
14        // BFS queue storing pairs of [current value, steps taken]
15        Queue<int[]> queue = new ArrayDeque<>();
16        queue.offer(new int[] {start, 0});
17      
18        // BFS to find minimum operations
19        while (!queue.isEmpty()) {
20            int[] current = queue.poll();
21            int currentValue = current[0];
22            int currentSteps = current[1];
23          
24            // Try each number in the array
25            for (int num : nums) {
26                // Try each operation (addition, subtraction, XOR)
27                for (IntBinaryOperator operation : operations) {
28                    int nextValue = operation.applyAsInt(currentValue, num);
29                  
30                    // Check if we've reached the goal
31                    if (nextValue == goal) {
32                        return currentSteps + 1;
33                    }
34                  
35                    // Only continue if the value is within valid range and unvisited
36                    if (nextValue >= 0 && nextValue <= 1000 && !visited[nextValue]) {
37                        queue.offer(new int[] {nextValue, currentSteps + 1});
38                        visited[nextValue] = true;
39                    }
40                }
41            }
42        }
43      
44        // No path found to reach the goal
45        return -1;
46    }
47}
48
1class Solution {
2public:
3    int minimumOperations(vector<int>& nums, int start, int goal) {
4        // Define pair type for storing (value, steps)
5        using pii = pair<int, int>;
6      
7        // Define three operations: addition, subtraction, and XOR
8        vector<function<int(int, int)>> operations{
9            [](int x, int y) { return x + y; },
10            [](int x, int y) { return x - y; },
11            [](int x, int y) { return x ^ y; },
12        };
13      
14        // Track visited values to avoid redundant processing (valid range: 0-1000)
15        vector<bool> visited(1001, false);
16      
17        // BFS queue storing pairs of (current_value, steps_taken)
18        queue<pii> bfsQueue;
19        bfsQueue.push({start, 0});
20      
21        // Perform BFS to find minimum operations
22        while (!bfsQueue.empty()) {
23            // Get current state from queue
24            auto [currentValue, currentSteps] = bfsQueue.front();
25            bfsQueue.pop();
26          
27            // Try each number in the array
28            for (int num : nums) {
29                // Apply each operation
30                for (auto operation : operations) {
31                    int nextValue = operation(currentValue, num);
32                  
33                    // Check if we reached the goal
34                    if (nextValue == goal) {
35                        return currentSteps + 1;
36                    }
37                  
38                    // If value is within valid range and not visited, add to queue
39                    if (nextValue >= 0 && nextValue <= 1000 && !visited[nextValue]) {
40                        bfsQueue.push({nextValue, currentSteps + 1});
41                        visited[nextValue] = true;
42                    }
43                }
44            }
45        }
46      
47        // Goal cannot be reached
48        return -1;
49    }
50};
51
1/**
2 * Finds the minimum number of operations to transform start value to goal value
3 * @param nums - Array of numbers to use in operations
4 * @param start - Starting value
5 * @param goal - Target value to reach
6 * @returns Minimum number of operations, or -1 if impossible
7 */
8function minimumOperations(nums: number[], start: number, goal: number): number {
9    const n: number = nums.length;
10  
11    // Define the three possible operations
12    const addOperation = (x: number, y: number): number => x + y;
13    const subtractOperation = (x: number, y: number): number => x - y;
14    const xorOperation = (x: number, y: number): number => x ^ y;
15  
16    // Array of operation functions for easy iteration
17    const operations: Array<(x: number, y: number) => number> = [
18        addOperation, 
19        subtractOperation, 
20        xorOperation
21    ];
22  
23    // Track visited states to avoid cycles (valid range is 0-1000)
24    const visited: boolean[] = new Array(1001).fill(false);
25  
26    // BFS queue storing [current value, steps taken]
27    const queue: Array<[number, number]> = [[start, 0]];
28    visited[start] = true;
29  
30    // Perform BFS to find shortest path
31    while (queue.length > 0) {
32        const [currentValue, currentSteps] = queue.shift()!;
33      
34        // Try each number in the array
35        for (let i = 0; i < n; i++) {
36            // Try each operation (add, subtract, xor)
37            for (let j = 0; j < operations.length; j++) {
38                const nextValue: number = operations[j](currentValue, nums[i]);
39              
40                // Check if we've reached the goal
41                if (nextValue === goal) {
42                    return currentSteps + 1;
43                }
44              
45                // Add valid unvisited states to the queue
46                if (nextValue >= 0 && nextValue <= 1000 && !visited[nextValue]) {
47                    visited[nextValue] = true;
48                    queue.push([nextValue, currentSteps + 1]);
49                }
50            }
51        }
52    }
53  
54    // Goal cannot be reached
55    return -1;
56}
57

Time and Space Complexity

Time Complexity: O(n * m * k) where n = 1001 (the range of valid values from 0 to 1000), m is the length of the nums array, and k = 3 (number of operations).

  • The BFS explores at most n = 1001 unique states (values from 0 to 1000) since we use a visited array to avoid revisiting states.
  • For each state popped from the queue, we iterate through all m numbers in nums.
  • For each number, we apply k = 3 operations (addition, subtraction, XOR).
  • Each operation and queue operation takes O(1) time.
  • Therefore, the total time complexity is O(1001 * m * 3) = O(m) since 1001 and 3 are constants.

Space Complexity: O(n) where n = 1001

  • The vis array uses O(1001) = O(1) space (constant).
  • The queue can contain at most 1001 unique values in the worst case, each with an associated step count, giving O(1001) = O(1) space.
  • The lambda functions and other variables use O(1) space.
  • Therefore, the total space complexity is O(1) since 1001 is a constant.

Note: When considering the constants as fixed bounds, both complexities can be expressed as O(m) for time and O(1) for space. However, if we consider the general problem where the valid range is [0, n], then time complexity would be O(n * m) and space complexity would be O(n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Not Checking Goal Immediately After Each Operation

The Pitfall: A common mistake is to only check if we've reached the goal when the result is within the [0, 1000] range. This would fail when the goal is outside this range.

# INCORRECT approach - only checks goal within valid range
if 0 <= next_value <= 1000:
    if next_value == goal:  # Wrong! Misses goals outside [0, 1000]
        return steps + 1
    if not visited[next_value]:
        queue.append((next_value, steps + 1))
        visited[next_value] = True

The Solution: Always check if next_value == goal immediately after computing it, regardless of whether it's in the valid range:

# CORRECT approach - checks goal before range validation
next_value = operation(current_value, num)

# Check goal first, regardless of range
if next_value == goal:
    return steps + 1

# Then handle valid range for further exploration
if 0 <= next_value <= 1000 and not visited[next_value]:
    queue.append((next_value, steps + 1))
    visited[next_value] = True

2. Marking Start as Visited Too Early

The Pitfall: Marking the start value as visited before processing it in the queue can cause issues if we need to revisit it through a different path.

# INCORRECT - marking start as visited before processing
visited = [False] * 1001
if 0 <= start <= 1000:
    visited[start] = True  # Wrong timing!
queue = deque([(start, 0)])

The Solution: Let the BFS naturally handle visited states. The start value will be processed from the queue, and any subsequent encounters will be properly marked:

# CORRECT - let BFS handle all visited marking
visited = [False] * 1001
queue = deque([(start, 0)])
# Start will be processed naturally in the BFS loop

3. Using a Set for Visited States Without Range Checking

The Pitfall: Using a set to track all visited values without considering memory constraints when values go far outside [0, 1000]:

# INCORRECT - can cause memory issues with large values
visited = set()
visited.add(start)

# Later in the loop...
if next_value not in visited:  # No range check!
    visited.add(next_value)  # Could add very large/small values
    queue.append((next_value, steps + 1))

The Solution: Use a fixed-size array for the valid range and only track visited states within [0, 1000]:

# CORRECT - bounded memory usage
visited = [False] * 1001

# Later in the loop...
if 0 <= next_value <= 1000 and not visited[next_value]:
    visited[next_value] = True
    queue.append((next_value, steps + 1))

4. Not Handling Edge Case Where Start Equals Goal

The Pitfall: Forgetting to check if start == goal initially, leading to unnecessary BFS traversal:

# INCORRECT - missing initial check
def minimumOperations(self, nums, start, goal):
    # Directly starts BFS without checking start == goal
    queue = deque([(start, 0)])
    # ... rest of BFS

The Solution: Add an initial check before starting BFS:

# CORRECT - handles start == goal efficiently
def minimumOperations(self, nums, start, goal):
    if start == goal:
        return 0
  
    queue = deque([(start, 0)])
    # ... rest of BFS
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How does merge sort divide the problem into subproblems?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More