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 oncex
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 fromnums
.
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
togoal
.
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:
- We're exploring states level by level (each level represents one more operation)
- The first time we reach the
goal
, we've found the shortest path - All edges have equal weight (each operation counts as 1 step)
- We need to track visited states to avoid redundant exploration
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:
-
Initialize the BFS: Start with a queue containing
(start, 0)
, where0
represents zero steps taken initially. -
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
)
-
BFS Exploration: While the queue is not empty:
- Dequeue the current state
(x, step)
- For each number
num
innums
:- Apply each of the three operations to get a new value
nx = op(x, num)
- Check for goal: If
nx == goal
, immediately returnstep + 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 thevis
array
- Add
- Apply each of the three operations to get a new value
- Dequeue the current state
-
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 EvaluatorExample 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 innums
. - 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 usesO(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
How does merge sort divide the problem into subproblems?
Recommended Readings
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!