Minimum Number of Operations to Make X and Y Equal
MedPrompt
You are given two positive integers x and y.
In one operation, you can do one of the four following operations:
- Divide
xby11ifxis a multiple of11. - Divide
xby5ifxis a multiple of5. - Decrement
xby1. - Increment
xby1.
Return the minimum number of operations required to make x and y equal.
Example 1:
Input: x = 26, y = 1
Output: 3
Explanation: We can make 26 equal to 1 by applying the following operations:
1. Decrement x by 1
2. Divide x by 5
3. Divide x by 5
It can be shown that 3 is the minimum number of operations required to make 26 equal to 1.Example 2:
Input: x = 54, y = 2
Output: 4
Explanation: We can make 54 equal to 2 by applying the following operations:
1. Increment x by 1
2. Divide x by 11
3. Divide x by 5
4. Increment x by 1
It can be shown that 4 is the minimum number of operations required to make 54 equal to 2.Example 3:
Input: x = 25, y = 30
Output: 5
Explanation: We can make 25 equal to 30 by applying the following operations:
1. Increment x by 1
2. Increment x by 1
3. Increment x by 1
4. Increment x by 1
5. Increment x by 1
It can be shown that 5 is the minimum number of operations required to make 25 equal to 30.
Constraints:
1 <= x, y <= 104
Approaches
3 approaches with complexity analysis and trade-offs.
A brute-force approach involves exploring every possible sequence of operations from x until y is reached. This can be framed as a recursive solution. We define a function that represents the state (the current number) and recursively calls itself for all possible next states that can be reached in one operation. The minimum of the results of these recursive calls is the answer for the current state.
Algorithm
- Define a recursive function, say
solve(currentX), that calculates the minimum operations fromcurrentXtoy. - The base case for the recursion is when
currentX == y, in which case the function returns 0. - In the recursive step, the function explores all four possible operations (increment, decrement, divide by 5, divide by 11) by making a recursive call for each valid resulting state.
- The function returns
1 +the minimum value returned by these recursive calls. - To prevent infinite loops (e.g.,
x -> x+1 -> x), this naive approach would require passing a set of visited nodes for the current path, making it even more complex and inefficient.
Walkthrough
This method models the problem as a state graph where each number is a node and each operation is an edge. The function solve(currentX) attempts to find the shortest path from currentX to y. It does this by trying every possible move: decrementing to currentX - 1, incrementing to currentX + 1, and, if applicable, dividing by 5 or 11. It then adds 1 to the result of the best move. The issue is that the same intermediate numbers (e.g., solve(10)) will be calculated over and over again through different paths, leading to an exponential number of calls.
// This is a conceptual representation. It will not pass due to performance issues.public int solve(int currentX, int y) { if (currentX == y) { return 0; } // A basic pruning to avoid going too far away from the target. // A robust solution would need more complex bounds. if (currentX <= 0 || currentX > 20000) { return 1_000_000_000; } // Explore all paths recursively int op1 = 1 + solve(currentX - 1, y); // Decrement int op2 = 1 + solve(currentX + 1, y); // Increment int minOps = Math.min(op1, op2); if (currentX % 5 == 0) { minOps = Math.min(minOps, 1 + solve(currentX / 5, y)); } if (currentX % 11 == 0) { minOps = Math.min(minOps, 1 + solve(currentX / 11, y)); } return minOps;}Complexity
Time
Exponential, roughly O(4^N), where N is the number of operations. The same states are computed multiple times.
Space
O(D), where D is the maximum depth of the recursion. In the worst case, this can be very large, leading to a `StackOverflowError`.
Trade-offs
Pros
Simple to understand the basic concept of exploring all paths.
Cons
Extremely inefficient due to re-computation of the same subproblems.
Will result in a 'Time Limit Exceeded' (TLE) error for all but the smallest inputs.
Prone to infinite recursion and stack overflow without careful (and complex) state management.
To fix the massive redundancy of the brute-force approach, we can use memoization, a dynamic programming technique. We store the result for each number we compute, so that if we need to compute it again, we can just look up the answer. This is essentially a Depth-First Search (DFS) on the state graph.
Algorithm
- Use a hash map or an array,
memo, to store the results of subproblems that have already been solved. - The recursive function,
solve(currentX), first checks if the result forcurrentXexists inmemo. If it does, it returns the stored value immediately. - If the value is not in
memo, it computes the result by recursively calling itself for all valid next states, just like the brute-force approach. - Before returning the computed result, it stores it in
memowithcurrentXas the key. - An important optimization: if
currentX <= y, the shortest path is to simply incrementcurrentXuntil it reachesyor decrementyuntil it reachescurrentX. The number of operations isy - currentX.
Walkthrough
This approach enhances the recursive solution by adding a cache (e.g., a HashMap) to store the minimum operations for any number i once computed. This prevents re-computation and reduces the time complexity from exponential to linear in the number of states. However, as a DFS-based approach, it explores one path to its full depth before backtracking. If x is significantly larger than y, a path involving many decrements (x -> x-1 -> x-2 ...) could lead to a very deep recursion stack, risking a StackOverflowError.
class Solution { private Map<Integer, Integer> memo; public int minimumOperations(int x, int y) { memo = new HashMap<>(); return solve(x, y); } private int solve(int currentX, int targetY) { if (currentX <= targetY) { return targetY - currentX; } if (memo.containsKey(currentX)) { return memo.get(currentX); } // Path 1: Decrement by 1. // This also serves as a baseline path cost. int res = 1 + solve(currentX - 1, targetY); // Path 2: Divide by 11, if possible. if (currentX % 11 == 0) { res = Math.min(res, 1 + solve(currentX / 11, targetY)); } // Path 3: Divide by 5, if possible. if (currentX % 5 == 0) { res = Math.min(res, 1 + solve(currentX / 5, targetY)); } // Path 4 & 5: Increment first, then divide. // This is tricky in DFS. A better way is to consider reaching the nearest multiples. // Cost to reach the nearest multiple of 11 (by decrementing) + 1 (for division) + result from there. res = Math.min(res, (currentX % 11) + 1 + solve(currentX / 11, targetY)); // Cost to reach the nearest multiple of 11 (by incrementing) + 1 (for division) + result from there. res = Math.min(res, (11 - (currentX % 11)) + 1 + solve(currentX / 11 + 1, targetY)); // Similarly for 5. res = Math.min(res, (currentX % 5) + 1 + solve(currentX / 5, targetY)); res = Math.min(res, (5 - (currentX % 5)) + 1 + solve(currentX / 5 + 1, targetY)); memo.put(currentX, res); return res; }}Note: The provided code demonstrates a more optimized recursive strategy than a simple 4-way branching DFS, but BFS remains superior in robustness.
Complexity
Time
O(S), where S is the number of states explored. In the worst case, this is proportional to the initial value of `x`.
Space
O(S) for the memoization table and O(S) for the recursion stack, where S is the number of states explored. This is roughly O(x).
Trade-offs
Pros
Drastically more efficient than brute-force recursion.
Guarantees that each state is computed only once.
Cons
While it fixes the re-computation issue, it is still a recursive approach.
Can suffer from
StackOverflowErrorif the recursion depth becomes too large, which can happen ifxis much larger thany.
Since this is a shortest path problem on an unweighted graph (all operations have a cost of 1), Breadth-First Search (BFS) is the most suitable and robust algorithm. BFS explores the state graph level by level, guaranteeing that the first time we reach the target y, it will be via a path with the minimum number of operations.
Algorithm
- Handle the base case: if
x <= y, the only optimal way is to incrementxy-xtimes. Returny-x. - For the main case
x > y, initialize a queue for BFS and add the starting numberx. - Use a map or an array
distto store the minimum operations to reach any number. Initializedist[x] = 0. This map also serves as avisitedset. - While the queue is not empty, dequeue a number
current. - If
current == y, we have found the shortest path. Returndist[current]. - Otherwise, generate all possible next states from
current:current - 1,current + 1,current / 5,current / 11. - For each valid
nextstate that has not been visited (i.e., not indist), update its distancedist[next] = dist[current] + 1and enqueue it.
Walkthrough
We start the search from x. A queue holds the numbers to visit, and a map or array stores the number of operations (distance) to reach each number from x. The distance map also handily doubles as a visited set, preventing cycles and redundant processing.
We begin by adding x to the queue with 0 operations. Then, we repeatedly extract a number from the queue, explore its neighbors (the numbers resulting from the four operations), and add any unvisited neighbors back into the queue with an incremented operation count. Because BFS explores layer by layer, the first time we encounter y, we are guaranteed to have done so in the minimum number of steps.
import java.util.Queue;import java.util.LinkedList;import java.util.Map;import java.util.HashMap; class Solution { public int minimumOperations(int x, int y) { if (x <= y) { return y - x; } Queue<Integer> queue = new LinkedList<>(); queue.offer(x); Map<Integer, Integer> dist = new HashMap<>(); dist.put(x, 0); while (!queue.isEmpty()) { int current = queue.poll(); int currentDist = dist.get(current); if (current == y) { return currentDist; } // Try all 4 operations int[] nextStates = {current - 1, current + 1}; for (int next : nextStates) { if (next > 0 && !dist.containsKey(next)) { dist.put(next, currentDist + 1); queue.offer(next); } } if (current % 11 == 0) { int next = current / 11; if (!dist.containsKey(next)) { dist.put(next, currentDist + 1); queue.offer(next); } } if (current % 5 == 0) { int next = current / 5; if (!dist.containsKey(next)) { dist.put(next, currentDist + 1); queue.offer(next); } } } return -1; // Should not be reached given the problem constraints }}Complexity
Time
O(S + E), where S is the number of states (nodes) and E is the number of transitions (edges). Since each node has at most 4 edges, this simplifies to O(S). The number of states is bounded by the values explored, roughly O(x).
Space
O(S), where S is the number of states explored. This is for the queue and the distance map. In the worst case, this is proportional to `x`.
Trade-offs
Pros
Guaranteed to find the shortest path in an unweighted graph.
It is an iterative approach, so it avoids the risk of
StackOverflowError.Generally the most efficient and reliable method for this category of problems.
Cons
May use more memory than a highly optimized recursive solution if the number of states at each level is large.
Can be slightly more code to write compared to a simple recursive function.
Solutions
Solution
class Solution {private Map<Integer, Integer> f = new HashMap<>();private int y;public int minimumOperationsToMakeEqual(int x, int y) { this.y = y; return dfs(x); }private int dfs(int x) { if (y >= x) { return y - x; } if (f.containsKey(x)) { return f.get(x); } int ans = x - y; int a = x % 5 + 1 + dfs(x / 5); int b = 5 - x % 5 + 1 + dfs(x / 5 + 1); int c = x % 11 + 1 + dfs(x / 11); int d = 11 - x % 11 + 1 + dfs(x / 11 + 1); ans = Math.min(ans, Math.min(a, Math.min(b, Math.min(c, d)))); f.put(x, ans); return ans; }}class Solution {public: int minimumOperationsToMakeEqual(int x, int y) { unordered_map<int, int> f; function<int(int)> dfs = [&](int x) { if (y >= x) { return y - x; } if (f.count(x)) { return f[x]; } int a = x % 5 + 1 + dfs(x / 5); int b = 5 - x % 5 + 1 + dfs(x / 5 + 1); int c = x % 11 + 1 + dfs(x / 11); int d = 11 - x % 11 + 1 + dfs(x / 11 + 1); return f[x] = min({x - y, a, b, c, d}); }; return dfs(x); }};class Solution: def minimumOperationsToMakeEqual(self, x: int, y: int) -> int: @ cache def dfs(x: int) -> int: if y >= x: return y - x ans = x - y ans = min(ans, x % 5 + 1 + dfs(x // 5)) ans = min(ans, 5 - x % 5 + 1 + dfs(x // 5 + 1)) ans = min(ans, x % 11 + 1 + dfs(x // 11)) ans = min(ans, 11 - x % 11 + 1 + dfs(x // 11 + 1)) return ans return dfs(x)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.