You have a primitive calculator that can transform a current number x using exactly one of the operations:
x -> x + 1 (add 1)
x -> 2 * x (multiply by 2)
x -> 3 * x (multiply by 3)
Given a target n (n >= 1), find the minimum number of operations needed to reach n starting from 1, and output one optimal sequence of intermediate numbers (including 1 and n). If multiple optimal sequences exist, any one is acceptable.
Input: n =96234Output: steps =14(one possible path)Path (example):[1,3,9,10,11,22,66,198,594,1782,5346,16038,16039,32078,96234]Explanation: Path length is minimal; other optimal variants exist.
We want the shortest sequence of transformations from 1 to n under operations +1, *2, *3. This is equivalent to shortest path in an implicit DAG if we consider transitions forward, or a reverse greedy/bfs using operations /2, /3, -1 when valid. A dynamic programming approach is straightforward and reconstructs the path.
f(1) = 0
f(n) = 1 + min( f(n-1), f(n/2) if n%2==0, f(n/3) if n%3==0 )
This explores all possible derivation trees. It recomputes overlapping subproblems many times → exponential time. It is useful only to illustrate the optimal substructure before introducing memoization / DP.
Extremely slow beyond small n (e.g., > 10^3 already impractical).
We usually compute ONLY the count here; constructing the full path naively would require returning and copying vectors at each step (further overhead).
intnaiveCount(int n) {
if (n <=1) return0;
int best = naiveCount(n-1);
if (n %2==0) best = min(best, naiveCount(n/2));
if (n %3==0) best = min(best, naiveCount(n/3));
return best +1;
}
1
2
3
4
5
6
7
funcnaiveCount(nint) int {
ifn<=1 { return0 }
best:=naiveCount(n-1)
ifn%2==0 { ifv:=naiveCount(n/2); v < best { best = v } }
ifn%3==0 { ifv:=naiveCount(n/3); v < best { best = v } }
returnbest+1}
1
2
3
4
5
6
7
intnaiveCount(int n) {
if (n <= 1) return 0;
int best = naiveCount(n - 1);
if (n % 2 == 0) best = Math.min(best, naiveCount(n / 2));
if (n % 3 == 0) best = Math.min(best, naiveCount(n / 3));
return best + 1;
}
1
2
3
4
5
6
7
8
9
defnaive_count(n: int) -> int:
if n <=1:
return0 best = naive_count(n -1)
if n %2==0:
best = min(best, naive_count(n //2))
if n %3==0:
best = min(best, naive_count(n //3))
return best +1
Add a cache to the naive recurrence. Each f(k) is computed once, giving O(n) states and O(1) transitions each (just up to three candidates). We also record the predecessor that achieved the minimum so we can rebuild the path – mirroring the logic of bottom-up DP but in a depth-first way.
dp = [-1]*(n+1)
parent = [0]*(n+1)
defsolve(x):
if x ==1: return0if dp[x] !=-1: return dp[x]
best_val = solve(x-1)
best_prev = x-1if x %2==0:
v = solve(x//2)
if v < best_val:
best_val, best_prev = v, x//2if x %3==0:
v = solve(x//3)
if v < best_val:
best_val, best_prev = v, x//3 dp[x] = best_val +1 parent[x] = best_prev
return dp[x]
solve(n)
path = []
cur = n
while cur >=1:
path.append(cur)
if cur ==1: break cur = parent[cur]
path.reverse()
from functools import lru_cache
from typing import List
classSolutionTopDown:
defminOperationsPath(self, n: int) -> List[int]:
if n <=1:
return [1]
parent = [0]*(n+1)
@lru_cache(None)
defsolve(x: int) -> int:
if x ==1:
return0 best_val = solve(x-1)
best_prev = x-1if x %2==0:
v = solve(x//2)
if v < best_val:
best_val, best_prev = v, x//2if x %3==0:
v = solve(x//3)
if v < best_val:
best_val, best_prev = v, x//3 parent[x] = best_prev
return best_val +1 solve(n)
path: List[int] = []
cur = n
while cur >=1:
path.append(cur)
if cur ==1:
break cur = parent[cur]
path.reverse()
return path
from typing import List
classSolution:
defminOperationsPath(self, n: int) -> List[int]:
if n <=1:
return [1]
dp = [0] * (n +1)
parent = [0] * (n +1)
for i in range(2, n +1):
best_prev = i -1 best_val = dp[i -1]
if i %2==0and dp[i //2] < best_val:
best_val = dp[i //2]
best_prev = i //2if i %3==0and dp[i //3] < best_val:
best_val = dp[i //3]
best_prev = i //3 dp[i] = best_val +1 parent[i] = best_prev
path: List[int] = []
cur = n
while cur >=1:
path.append(cur)
cur = parent[cur]
path.reverse()
return path
Starting from n, we can move backwards using inverse operations: if x divisible by 3 go to x/3, if divisible by 2 go to x/2, always can go to x-1.
This forms an unweighted graph; BFS finds the shortest path length. However, worst-case exploring many numbers near n makes this comparable or worse than linear DP. Included for conceptual symmetry.
from collections import deque
from typing import List
classSolutionBFS:
defminOperationsPath(self, n: int) -> List[int]:
if n <=1:
return [1]
q = deque([n])
parent = {n: None}
while q:
x = q.popleft()
if x ==1:
breakif x %3==0and x //3notin parent:
parent[x //3] = x
q.append(x //3)
if x %2==0and x //2notin parent:
parent[x //2] = x
q.append(x //2)
if x -1notin parent:
parent[x -1] = x
q.append(x -1)
path = []
cur =1while cur isnotNone:
path.append(cur)
cur = parent[cur]
path.reverse() # now 1..nreturn path