You are given an array stones
of length n
, where each element represents the position of a stone on the X-axis. Each position is unique and all stones are placed at different integer coordinates.
In one move, you can pick up a stone at position stones[i]
and move it to an unoccupied position between the current minimum and maximum positions of all stones (exclusive). That is, you cannot move a stone to a position outside the range defined by the smallest and largest stone positions, nor can you move it to a position already occupied by another stone.
The goal is to make the stones' positions consecutive (i.e., form a sequence of n
consecutive integers). You are asked to return two integers:
Constraints:
stones[i]
are distinctAt first glance, the problem seems to suggest brute-forcing every possible move sequence, but with up to 10,000 stones, that's computationally infeasible. Instead, we notice a few key things:
For the maximum number of moves, we can always move stones one at a time from the ends inward, filling in the largest available gaps. For the minimum number of moves, we need to find the "largest cluster" of stones that are already consecutive (or nearly so), and see how few moves it takes to fill the rest.
This suggests using sorting and sliding window techniques for optimization, rather than brute force.
Let's break down the steps to solve the problem efficiently:
n
stones, the total number of positions between the endpoints is stones[n-1] - stones[0] + 1
. The number of empty positions is stones[n-1] - stones[0] + 1 - n
.stones[n-2] - stones[0] - (n-2)
and stones[n-1] - stones[1] - (n-2)
.n
that contains the most stones. The minimum number of moves is n
minus the size of the largest such cluster.n-1
), and count how many stones are in this window.n-1
stones and is only missing one position at the end (i.e., the gap is exactly n-2
), then you need 2 moves (not 1), because you can't move a stone from the end directly into the gap.We use sorting for O(n log n) time, and a sliding window for O(n) time, making the entire approach efficient.
Example Input: stones = [6, 5, 4, 3, 10]
[3, 4, 5, 6, 10]
10 - 3 + 1 = 8
8 - 5 = 3
stones[3] - stones[0] - (n-2) = 6 - 3 - 3 = 0
stones[4] - stones[1] - (n-2) = 10 - 4 - 3 = 3
max(0, 3) = 3
[1, 3]
The problem asks us to make stones consecutive with as few and as many moves as possible. By sorting the stones and using a sliding window, we can efficiently determine the minimal moves needed to cluster them, and by considering the largest endpoint gaps, we find the maximal moves. The key insight is to focus on clusters and gaps, rather than simulating every possible move, which leads to an elegant and efficient O(n log n) solution.
class Solution:
def numMovesStonesII(self, stones):
stones.sort()
n = len(stones)
max_moves = max(
stones[-1] - stones[1] - n + 2,
stones[-2] - stones[0] - n + 2
)
min_moves = n
left = 0
for right in range(n):
# Move left pointer to keep window size <= n
while stones[right] - stones[left] + 1 > n:
left += 1
current = right - left + 1
if current == n - 1 and stones[right] - stones[left] + 1 == n - 1:
# Special case: need 2 moves instead of 1
min_moves = min(min_moves, 2)
else:
min_moves = min(min_moves, n - current)
return [min_moves, max_moves]
class Solution {
public:
vector<int> numMovesStonesII(vector<int>& stones) {
sort(stones.begin(), stones.end());
int n = stones.size();
int maxMoves = max(stones[n-2] - stones[0] - n + 2,
stones[n-1] - stones[1] - n + 2);
int minMoves = n;
int left = 0;
for (int right = 0; right < n; ++right) {
while (stones[right] - stones[left] + 1 > n) {
++left;
}
int current = right - left + 1;
if (current == n - 1 && stones[right] - stones[left] + 1 == n - 1) {
minMoves = min(minMoves, 2);
} else {
minMoves = min(minMoves, n - current);
}
}
return {minMoves, maxMoves};
}
};
import java.util.Arrays;
class Solution {
public int[] numMovesStonesII(int[] stones) {
Arrays.sort(stones);
int n = stones.length;
int maxMoves = Math.max(stones[n-2] - stones[0] - n + 2,
stones[n-1] - stones[1] - n + 2);
int minMoves = n;
int left = 0;
for (int right = 0; right < n; ++right) {
while (stones[right] - stones[left] + 1 > n) {
left++;
}
int current = right - left + 1;
if (current == n - 1 && stones[right] - stones[left] + 1 == n - 1) {
minMoves = Math.min(minMoves, 2);
} else {
minMoves = Math.min(minMoves, n - current);
}
}
return new int[]{minMoves, maxMoves};
}
}
var numMovesStonesII = function(stones) {
stones.sort((a, b) => a - b);
const n = stones.length;
let maxMoves = Math.max(
stones[n-2] - stones[0] - n + 2,
stones[n-1] - stones[1] - n + 2
);
let minMoves = n;
let left = 0;
for (let right = 0; right < n; ++right) {
while (stones[right] - stones[left] + 1 > n) {
left++;
}
let current = right - left + 1;
if (current === n - 1 && stones[right] - stones[left] + 1 === n - 1) {
minMoves = Math.min(minMoves, 2);
} else {
minMoves = Math.min(minMoves, n - current);
}
}
return [minMoves, maxMoves];
};