1.端点石子移动问题
问题描述
小S正在玩一个关于石子的游戏,游戏中给定了一些石子,它们位于一维数轴上的不同位置,位置用数组 stones 表示。如果某个石子处于最小或最大的一个位置,我们称其为端点石子。
在每个回合,小S可以将一颗端点石子移动到一个未占用的位置,使其不再是端点石子。值得注意的是,如果石子的位置是连续的,则游戏结束,因为没有可以进行的移动操作。
你需要帮助小S找到可以移动的最小次数。
测试样例
样例1:
输入:
stones = [7, 4, 9]
输出:1
样例2:
输入:
stones = [6, 5, 4, 3, 10]
输出:2
样例3:
输入:
stones = [1, 2, 3, 4, 5]
输出:0
2.解题思路
小S需要将一维数轴上的石子位置调整成连续的,且每次只能移动端点石子(即位于数轴最小或最大位置的石子)。目标是通过最少的移动次数使石子位置变得连续,或者判断是否已经是连续状态。
解题步骤
-
排序石子位置: 由于数组
stones给出的是无序的石子位置,因此首先对它进行排序,方便后续连续性判断和窗口操作。 -
判断是否已连续:
- 排序后,如果首尾石子位置之间的间距恰好等于石子总数减一(即
stones[n-1] - stones[0] + 1 == n),则石子已经连续,不需要任何移动,返回 0。
- 排序后,如果首尾石子位置之间的间距恰好等于石子总数减一(即
-
滑动窗口找最小移动次数:
- 使用滑动窗口算法遍历石子的排列,找到包含最多石子的连续区间。滑动窗口长度设置为石子总数
n,计算每个窗口内的石子数量并找出窗口外的空位数量(即非连续部分)。 - 滑动窗口遍历过程中,
j为窗口右边界,i为左边界。在每个位置i处,将窗口扩大到i + n的位置。窗口内石子数量与n的差值即为移动次数。
- 使用滑动窗口算法遍历石子的排列,找到包含最多石子的连续区间。滑动窗口长度设置为石子总数
-
特殊情况处理:
- 如果窗口内只差一个空位(即当前窗口内石子间距刚好差 1 的连续情况),需要特殊处理,因为此种情况下可能要多移一步。
3.代码实现
import java.util.Arrays;
public class Main {
public static int solution(int[] stones) {
int n = stones.length;
Arrays.sort(stones);
// 检查是否已经是连续的
if (stones[n - 1] - stones[0] + 1 == n) return 0;
// 滑动窗口找最小移动次数
int minMoves = n; // 初始化为一个大值
int j = 0;
for (int i = 0; i < n; i++) {
while (j < n && stones[j] < stones[i] + n) j++;
int currentMoves = n - (j - i);
if (currentMoves == 1 && stones[j - 1] - stones[i] + 1 == n - 1) currentMoves++; // 特殊情况
minMoves = Math.min(minMoves, currentMoves);
}
return minMoves;
}
public static void main(String[] args) {
System.out.println(solution(new int[]{7, 4, 9}) == 1);
System.out.println(solution(new int[]{6, 5, 4, 3, 10}) == 2);
System.out.println(solution(new int[]{1, 2, 3, 4, 5}) == 0);
}
}
这段代码的核心目标是找出石子位置最少移动次数,以使所有石子在数轴上连续。代码首先对石子位置数组 stones 进行排序,以便按位置顺序处理。接着通过滑动窗口的方法检查石子之间的空隙数量,窗口长度为石子总数 n。滑动窗口遍历每个可能的连续子区间,以确定包含最大石子数的区间,这样可以找出需要移动的最少石子数量。最终返回最小移动次数,特例是在窗口内只差一个位置时的情况,这种情况下可能需额外一步调整。
4.复杂度分析
排序 stones 的时间复杂度为 O(nlogn)O(n \log n)O(nlogn),滑动窗口操作的时间复杂度为 O(n)O(n)O(n),因此总时间复杂度为 O(nlogn)O(n \log n)O(nlogn)。