1033. Moving Stones Until Consecutive
Problem Description
You have three stones placed at different positions on a number line. The positions are given as three integers a
, b
, and c
.
In each move, you must:
- Pick up a stone from either the leftmost or rightmost position
- Place it at any empty integer position between the other two stones
For example, if stones are at positions x
, y
, z
where x < y < z
:
- You can pick the stone at position
x
and move it to any positionk
wherex < k < z
andk ≠ y
- Or you can pick the stone at position
z
and move it to any positionk
wherex < k < z
andk ≠ y
The game ends when the three stones occupy consecutive positions (like 5, 6, 7).
Your task is to find:
- The minimum number of moves needed to end the game
- The maximum number of moves possible to end the game
Return these two values as an array [minimum_moves, maximum_moves]
.
Example: If stones are at positions 1, 2, and 5:
- Minimum moves: 1 (move stone from 5 to 3, resulting in consecutive positions 1, 2, 3)
- Maximum moves: 2 (first move 1 to 4 giving 2, 4, 5, then move 5 to 3 giving 2, 3, 4)
Intuition
Let's first sort the stones to positions x
, y
, z
where x < y < z
.
For the minimum moves, we need to think about the fastest way to get consecutive positions:
- If the stones are already consecutive (
z - x = 2
), we need 0 moves - If two stones are already consecutive or have just one gap between them (like 1,2,4 or 1,3,4), we can finish in just 1 move by placing the third stone next to them
- Otherwise, we need 2 moves - first bring one endpoint stone closer to create a gap of size 2 or less, then finish with the second move
The key insight is checking if y - x ≤ 2
or z - y ≤ 2
. If either gap is at most 2, we can finish in 1 move.
For the maximum moves, we want to move stones as many times as possible before they become consecutive. The total number of empty positions between x
and z
is z - x - 1
. Since two positions are already occupied by x
and z
, and one by y
, the number of empty positions we can use is z - x - 1 - 2 = z - x - 3
. But we need to end with consecutive positions, which means we'll eventually use up all empty spaces except one final configuration. So the maximum moves is actually z - x - 2
.
Think of it this way: we keep moving the endpoint stones one position at a time toward the middle, using up all available empty positions until the three stones are finally consecutive.
Learn more about Math patterns.
Solution Approach
The implementation follows our intuition by first sorting the three positions to establish x
(minimum), y
(middle), and z
(maximum).
x, z = min(a, b, c), max(a, b, c)
y = a + b + c - x - z
The clever trick here is calculating y
using the sum: since we know x
and z
, and the sum of all three is a + b + c
, we can find the middle value as y = a + b + c - x - z
.
Next, we check if the stones need any moves at all:
if z - x > 2:
If z - x = 2
, the stones are already consecutive (e.g., positions 5, 6, 7), so both minimum and maximum moves are 0.
For the minimum moves when z - x > 2
:
mi = 1 if y - x < 3 or z - y < 3 else 2
This checks:
- If
y - x < 3
(gap of 0, 1, or 2 between first two stones) - Or if
z - y < 3
(gap of 0, 1, or 2 between last two stones)
If either condition is true, we can finish in 1 move. Otherwise, we need 2 moves.
For the maximum moves:
mx = z - x - 2
This represents the total number of empty positions we can utilize. Between positions x
and z
, there are z - x - 1
total positions. Subtracting the position occupied by y
and accounting for the final consecutive arrangement, we get z - x - 2
maximum moves.
The solution returns [mi, mx]
as the final answer, handling the edge case where stones are already consecutive by initializing both values to 0.
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 the solution with stones at positions 2, 4, and 8.
Step 1: Sort and identify positions
x = 2
(leftmost)y = 4
(middle)z = 8
(rightmost)
Step 2: Check if moves are needed
z - x = 8 - 2 = 6
, which is greater than 2- So the stones are not consecutive yet, moves are needed
Step 3: Calculate minimum moves
- Check gap between first two stones:
y - x = 4 - 2 = 2
(which is less than 3) ✓ - Check gap between last two stones:
z - y = 8 - 4 = 4
(which is not less than 3) ✗
Since the first gap is small enough (≤ 2), we can finish in 1 move:
- Move the stone from position 8 to position 3
- Result: positions 2, 3, 4 (consecutive!)
Step 4: Calculate maximum moves
- Maximum moves =
z - x - 2 = 8 - 2 - 2 = 4
To achieve maximum moves, we'd move stones one position at a time:
- Move stone from 2 to 5 → positions become 4, 5, 8
- Move stone from 8 to 6 → positions become 4, 5, 6 (consecutive!)
Wait, that's only 2 moves. Let's try a different sequence:
- Move stone from 2 to 7 → positions become 4, 7, 8
- Move stone from 4 to 6 → positions become 6, 7, 8 (consecutive!)
Actually, let me reconsider. The maximum should use all available empty spaces:
- Move stone from 2 to 6 → positions become 4, 6, 8
- Move stone from 8 to 5 → positions become 4, 5, 6 (consecutive!)
This gives us 2 moves, but the formula says 4. The key insight is that we can move stones incrementally:
- Move from 2 to 3 → positions: 3, 4, 8
- Move from 8 to 5 → positions: 3, 4, 5 (consecutive!)
Or to truly maximize:
- Move from 2 to 7 → positions: 4, 7, 8
- Move from 4 to 6 → positions: 6, 7, 8 (consecutive!)
The formula z - x - 2
actually represents the maximum number of distinct positions we can visit. With positions 2, 4, 8, we have 4 empty spots between them (3, 5, 6, 7). We can use these spots strategically to make up to 4 moves total before reaching a consecutive arrangement.
Final Answer: [1, 4]
Solution Implementation
1from typing import List
2
3class Solution:
4 def numMovesStones(self, a: int, b: int, c: int) -> List[int]:
5 # Sort the three positions to get left, middle, and right stones
6 left = min(a, b, c)
7 right = max(a, b, c)
8 middle = a + b + c - left - right # Calculate middle value without sorting
9
10 # Initialize minimum and maximum moves
11 min_moves = 0
12 max_moves = 0
13
14 # Check if stones are not already consecutive
15 if right - left > 2:
16 # For minimum moves:
17 # If either gap (middle-left or right-middle) is <= 2, we can do it in 1 move
18 # Otherwise, we need 2 moves
19 if middle - left <= 2 or right - middle <= 2:
20 min_moves = 1
21 else:
22 min_moves = 2
23
24 # For maximum moves:
25 # We can move stones one position at a time
26 # Total empty spaces between leftmost and rightmost stones
27 max_moves = right - left - 2
28
29 return [min_moves, max_moves]
30
1class Solution {
2 public int[] numMovesStones(int a, int b, int c) {
3 // Sort the three positions to get leftmost, middle, and rightmost stones
4 int leftmost = Math.min(a, Math.min(b, c));
5 int rightmost = Math.max(a, Math.max(b, c));
6 int middle = a + b + c - leftmost - rightmost;
7
8 // Initialize minimum and maximum moves
9 int minimumMoves = 0;
10 int maximumMoves = 0;
11
12 // Check if stones are not already consecutive (gap > 2 means at least one empty space)
13 if (rightmost - leftmost > 2) {
14 // Calculate minimum moves:
15 // If either gap (middle-left or right-middle) is <= 2, we can finish in 1 move
16 // Otherwise, we need 2 moves
17 if (middle - leftmost <= 2 || rightmost - middle <= 2) {
18 minimumMoves = 1;
19 } else {
20 minimumMoves = 2;
21 }
22
23 // Calculate maximum moves:
24 // Total empty spaces between leftmost and rightmost stones
25 maximumMoves = rightmost - leftmost - 2;
26 }
27
28 return new int[] {minimumMoves, maximumMoves};
29 }
30}
31
1class Solution {
2public:
3 vector<int> numMovesStones(int a, int b, int c) {
4 // Sort the three positions to get leftmost, middle, and rightmost stones
5 int leftmost = min({a, b, c});
6 int rightmost = max({a, b, c});
7 int middle = a + b + c - leftmost - rightmost; // Calculate middle by subtraction
8
9 // Initialize minimum and maximum moves needed
10 int minMoves = 0;
11 int maxMoves = 0;
12
13 // Check if stones are not already consecutive (gap > 2 means not consecutive)
14 if (rightmost - leftmost > 2) {
15 // Calculate minimum moves:
16 // If either gap (middle-left or right-middle) is <= 2, we can make them consecutive in 1 move
17 // Otherwise, we need 2 moves (move each endpoint stone next to middle)
18 if (middle - leftmost <= 2 || rightmost - middle <= 2) {
19 minMoves = 1;
20 } else {
21 minMoves = 2;
22 }
23
24 // Maximum moves: total empty spaces between leftmost and rightmost stones
25 // We can move stones one position at a time to fill all gaps
26 maxMoves = rightmost - leftmost - 2;
27 }
28 // If stones are already consecutive (rightmost - leftmost <= 2), both min and max remain 0
29
30 return {minMoves, maxMoves};
31 }
32};
33
1/**
2 * Calculates the minimum and maximum number of moves to bring three stones together
3 * @param a - Position of first stone
4 * @param b - Position of second stone
5 * @param c - Position of third stone
6 * @returns Array containing [minimum moves, maximum moves]
7 */
8function numMovesStones(a: number, b: number, c: number): number[] {
9 // Sort the three positions to get leftmost, middle, and rightmost stones
10 const leftPosition: number = Math.min(a, Math.min(b, c));
11 const rightPosition: number = Math.max(a, Math.max(b, c));
12 const middlePosition: number = a + b + c - leftPosition - rightPosition;
13
14 // Initialize minimum and maximum move counters
15 let minimumMoves: number = 0;
16 let maximumMoves: number = 0;
17
18 // Check if stones are not already consecutive
19 if (rightPosition - leftPosition > 2) {
20 // Calculate minimum moves:
21 // - If either gap (left-to-middle or middle-to-right) is 1 or 2, only 1 move needed
22 // - Otherwise, 2 moves are needed
23 if (middlePosition - leftPosition < 3 || rightPosition - middlePosition < 3) {
24 minimumMoves = 1;
25 } else {
26 minimumMoves = 2;
27 }
28
29 // Maximum moves: total empty positions between leftmost and rightmost stones
30 maximumMoves = rightPosition - leftPosition - 2;
31 }
32
33 return [minimumMoves, maximumMoves];
34}
35
Time and Space Complexity
Time Complexity: O(1)
The algorithm performs a constant number of operations:
- Finding the minimum and maximum of three numbers using
min()
andmax()
functions -O(1)
- Computing
y
using arithmetic operations -O(1)
- Conditional checks and arithmetic operations for calculating
mi
andmx
-O(1)
All operations are basic arithmetic and comparisons on fixed-size input (three integers), resulting in constant time complexity.
Space Complexity: O(1)
The algorithm uses only a fixed amount of extra space:
- Variables
x
,y
,z
to store the sorted positions -O(1)
- Variables
mi
andmx
to store the minimum and maximum moves -O(1)
- The output list containing two integers -
O(1)
The space usage does not scale with input size, as the input is always exactly three integers, resulting in constant space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Gap Calculation for Minimum Moves
A common mistake is using < 3
(less than 3) when checking gaps for the minimum moves condition, when the correct check should be <= 2
(less than or equal to 2).
Incorrect Implementation:
# Wrong: using < 3 which includes gap of 2 if middle - left < 3 or right - middle < 3: min_moves = 1
Why it's wrong:
- A gap of 2 means there's exactly one empty position between two stones (e.g., stones at 1 and 4 have positions 2 and 3 between them, gap = 4-1 = 3)
- With a gap of 2 (one empty position), we can complete in one move
- Using
< 3
would incorrectly include gaps of exactly 2, treating them the same as gaps of 0 or 1
Correct Implementation:
# Correct: gap <= 2 means at most one empty position between stones if middle - left <= 2 or right - middle <= 2: min_moves = 1
Pitfall 2: Off-by-One Error in Maximum Moves Calculation
Another frequent error is incorrectly calculating the maximum number of moves by not properly accounting for the occupied positions.
Incorrect Attempts:
# Wrong: Forgetting to subtract occupied positions max_moves = right - left - 1 # Wrong: Not accounting for the final consecutive arrangement max_moves = right - left - 3
Correct Calculation:
max_moves = right - left - 2
The formula right - left - 2
accounts for:
- Total positions between left and right:
right - left - 1
- Minus the middle stone's position:
-1
- Result:
right - left - 2
empty positions that can be utilized
Pitfall 3: Not Handling Already Consecutive Stones
Failing to check if stones are already consecutive before calculating moves:
Problematic Code:
def numMovesStones(self, a: int, b: int, c: int) -> List[int]:
left = min(a, b, c)
right = max(a, b, c)
middle = a + b + c - left - right
# Missing check for already consecutive stones!
if middle - left <= 2 or right - middle <= 2:
min_moves = 1
else:
min_moves = 2
max_moves = right - left - 2
return [min_moves, max_moves]
This would return [1, 0]
for already consecutive stones like [5, 6, 7], when it should return [0, 0]
.
Solution: Always check if stones are already consecutive first:
if right - left == 2: # Stones are consecutive return [0, 0]
Which data structure is used to implement recursion?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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!