101. Minimum Rotations to Type a String with Multiple Circular Dials
You are given k circular dials containing the letters A through Z and a target string. Every dial initially points to A. In one move, rotate one dial one step clockwise or counterclockwise. A target character can be typed when at least one dial points to that character. Find the minimum total number of rotations required to type the target string in order, and explain the state representation, transition choices, edge cases, and complexity.
I would use dynamic programming over the dial positions. A state is a sorted tuple of the current positions of all k dials. I start with every dial at A. For each target character, I try moving every dial, add the shorter circular distance, sort the new positions, and keep the lowest cost for each state. This preserves all meaningful choices and avoids an unsafe greedy decision. The shown code takes O(n · S · k² log k) time and O(S · k) auxiliary space.
See the Code while reading this explanation.
We need to type the target string in order using k circular dials. Every dial starts at A. Moving one dial by one letter costs one rotation. A greedy choice is not always safe because the cheapest move now can create a worse dial arrangement for later characters. The diagram uses dynamic programming to keep the minimum cost for every reachable canonical dial configuration.
- What input sizes, value ranges, and edge cases should the solution handle?
- What output should be returned for empty, invalid, or duplicate input?
- Should I prioritize execution time or memory use, and may I use the standard library?
The input contains an integer k and a target string made from uppercase letters A through Z.
Each of the k dials starts at A, which has index 0. The remaining letters use indices 1 through 25.
For each target character, at least one dial must point to that character. Typing the character itself has no extra cost. We return the minimum total number of rotations needed to type the complete target string in order.
In the diagram example, k = 2 and target = "CBC". The minimum result is 3.
A state is a sorted tuple containing the current positions of all k dials.
For example, the state (0, 2) means that one dial points to A and one dial points to C.
The tuple is sorted because the physical names of identical dials do not affect future costs. The configurations (0, 2) and (2, 0) represent the same useful arrangement, so they should share one state.
The dictionary dp maps each canonical state to the minimum cost needed to type the target prefix processed so far and finish in that state.
The central invariant is: after each processed character, dp[state] stores the lowest cost for typing that prefix and ending with exactly that canonical dial configuration.
Every dial starts at A, so every position is 0.
For k = 2, the initial state is (0, 0).
The initial dictionary is {(0, 0): 0}. The cost is 0 because no dial has moved and no target character has been processed.
For each target character, we convert it to an index from 0 through 25.
For every current state, we try moving every tuple entry to the target position. Tuple-entry indices are temporary positions inside the sorted state. They are not permanent physical dial identities.
The circular movement cost from position a to position b is:
min(|a - b|, 26 - |a - b|)
This chooses the shorter movement direction around the circular alphabet.
After moving one dial, we sort the resulting k positions. This creates the canonical next state. If different transitions reach the same state, we keep only the smallest total cost.
The input is k = 2 and target = "CBC".
We start with dp = {(0, 0): 0}.
The first character is C, which has index 2. Moving either dial from A to C costs 2. Both choices become the same sorted state (0, 2). The new dictionary is {(0, 2): 2}.
The second character is B, which has index 1. From state (0, 2), moving the tuple entry at position 0 from A to B costs 1 and creates canonical state (1, 2) with total cost 3. Moving the tuple entry at position 1 from C to B also costs 1 and creates canonical state (0, 1) with total cost 3. The new dictionary is {(0, 1): 3, (1, 2): 3}.
The final character is C, which has index 2.
From state (0, 1) with cost 3, moving A to C costs 2 and produces (1, 2) with total cost 5. Moving B to C costs 1 and produces (0, 2) with total cost 4.
From state (1, 2) with cost 3, moving B to C costs 1 and produces (2, 2) with total cost 4. One dial already points to C, so selecting that tuple entry costs 0 and keeps canonical state (1, 2) with total cost 3.
The final states include (1, 2) with cost 3, (0, 2) with cost 4, and (2, 2) with cost 4. The minimum is 3.
One concrete optimal physical sequence before canonical sorting is to move Dial 1 from A to C for cost 2, move Dial 2 from A to B for cost 1, and reuse Dial 1 at C for cost 0. The final answer is 3.
At every target character, the algorithm tries every possible dial move from every reachable state.
It discards a path only when another path reaches the same canonical state with a lower cost. Therefore, no cheaper meaningful configuration is lost.
By induction, after every processed prefix, each stored state has its minimum possible cost. After the complete target is processed, the minimum value in dp is the globally minimum number of rotations.
The code uses one dictionary for the current target prefix and another dictionary for the next prefix. Each dictionary key is a sorted tuple of dial positions. Each value is the minimum cost for that state.
Let n be the target length and S be the number of reachable canonical states during one target step. Each of the S states tries k dial moves. For each move, the code copies k positions and sorts the resulting tuple in O(k log k) time. The shown code therefore takes O(n · S · k² log k) time.
The dictionaries can store up to S tuples, and each tuple contains k positions. The auxiliary space is O(S · k). Across all possible configurations, S is at most C(k + 25, k), although the reachable set for one target prefix may be smaller.
Important edge cases are an empty target, a character already covered by a dial, clockwise and counterclockwise wrap-around, repeated characters, and k = 1.
The key insight is that the cheapest immediate move is not always part of the cheapest complete sequence. Each move changes the dial arrangement, and that arrangement affects later characters. Dynamic programming preserves these future choices. The state is a sorted tuple of all dial positions. Sorting removes unnecessary identity from identical dials, so equivalent arrangements share one state. For each target character, the algorithm tries moving every dial from every current state, adds the circular distance, and retains the minimum cost for each resulting canonical state. The invariant is that dp[state] is the minimum cost for typing the processed prefix and ending in that state.
from typing import Dict, Tuple
def min_rotations(k: int, target: str) -> int:
initial = tuple([0] * k)
dp: Dict[Tuple[int, ...], int] = {initial: 0}
for ch in target:
target_pos = ord(ch) - ord("A")
next_dp: Dict[Tuple[int, ...], int] = {}
for state, current_cost in dp.items():
for dial_index in range(k):
current_pos = state[dial_index]
diff = abs(current_pos - target_pos)
move_cost = min(diff, 26 - diff)
positions = list(state)
positions[dial_index] = target_pos
next_state = tuple(sorted(positions))
candidate = current_cost + move_cost
if candidate < next_dp.get(next_state, float("inf")):
next_dp[next_state] = candidate
dp = next_dp
return min(dp.values(), default=0)
if __name__ == "__main__":
k = 2
target = "CBC"
print(min_rotations(k, target)) # 3Let n be the number of characters in the target. Let S be the number of reachable canonical dial configurations during one target step. For each character, the code processes up to S states. From each state, it tries k dial entries. For each choice, it copies k positions and sorts them in O(k log k) time. Therefore, the shown code takes O(n · S · k² log k) time. Python dictionary lookup and update are O(1) on average. The dictionaries store up to S tuples of length k, so the auxiliary space is O(S · k). Across all configurations, S is at most C(k + 25, k).
This pattern is useful when several interchangeable resources can handle an ordered sequence of requests and each choice changes the cost of later choices. Examples include movable cursors, robotic arms, machine heads, or identical workers assigned to ordered tasks. Canonical states are useful when resource names do not affect future decisions and equivalent arrangements can be merged.
This question tests whether the candidate can recognize when a greedy choice is unsafe and replace it with dynamic programming. It also checks state design, transition reasoning, circular-distance calculation, and the ability to remove unnecessary identity through canonical sorting. The interviewer can evaluate whether the candidate preserves all meaningful choices, maintains a clear invariant, writes correct dictionary updates, handles wrap-around and repeated characters, and includes tuple copying and sorting in the complexity analysis.
A common mistake is choosing the dial with the smallest immediate rotation cost. That greedy choice can produce a worse arrangement for later characters. Another mistake is keeping permanent physical dial identities in the canonical dynamic programming state. Equivalent arrangements should be sorted into one tuple. Candidates reaching the same state must keep only the lowest cost. It is also easy to forget circular wrap-around and use only the direct distance. Finally, the complexity must include copying and sorting each k-position tuple.
Define dp[state] before writing code. Say that state is a sorted tuple and dp[state] is the minimum cost after the processed prefix. Then explain one transition and why canonical sorting safely merges equivalent dial arrangements.










