第480题 端点石子移动问题
题目分析
给定一个非递减排序的数组 A,需要判断是否存在两个不同的、长度相同的子数组,它们的元素总和相等。子数组可题目描述了一种石子排列的游戏,给出了一组石子在一维数轴上的不同位置,目标是通过最少的移动次数,使这些石子的位置连续。每次移动可以将端点石子移动到未占用的位置,但不能移到当前其他石子的位置。游戏在石子排列成连续位置时结束,因此我们需要计算最少的移动次数。
题目给出的测试样例让我们观察到:
- 当石子的位置本来就连续时,无需移动,移动次数为 0。
- 当石子的位置分布较为零散时,需要通过移动端点石子来使它们变得连续
解题思路
-
排序:首先对石子的位置数组进行排序,使石子的位置按照从小到大的顺序排列。这样可以方便地找到连续窗口。
-
滑动窗口:由于石子数量是固定的,我们可以使用一个长度为 nnn 的滑动窗口在排好序的石子位置数组上滑动,检查每个窗口内的石子能否形成连续的排列。
-
最小移动次数:
- 使用滑动窗口计算窗口内已经有的连续石子数,如果窗口内有 kkk 个石子已经连续,那么将其他 n−kn - kn−k 个石子移入窗口即可完成连续排列。
- 特殊情况处理:当窗口内有 n−1n - 1n−1 个石子已连续并且窗口大小为 n−1n - 1n−1 时,特殊情况下可能只需要 2 步移动
解题代码
def solution(stones: list) -> int:
stones.sort()
stones.sort()
n = len(stones)
min_moves = n
j = 0
for i in range(n):
while j < n and stones[j] - stones[i] + 1 <= n:
j += 1
already_continuous = j - i
if already_continuous == n - 1 and stones[j - 1] - stones[i] + 1 == n - 1:
min_moves = min(min_moves, 2)
else:
min_moves = min(min_moves, n - already_continuous)
return min_moves
if __name__ == '__main__':
print(solution(stones=[7, 4, 9]) == 1)
print(solution(stones=[6, 5, 4, 3, 10]) == 2)
print(solution(stones=[1, 2, 3, 4, 5]) == 0)
模块解释
代码分为以下几个模块:
- 排序模块:
stones.sort(),对输入石子位置排序,确保后续滑动窗口操作可以顺利进行。 - 滑动窗口模块:通过双指针构建滑动窗口,滑动过程中每次统计窗口内的连续石子数。窗口移动到一个新位置后,检查窗口内的石子数量与连续的需求是否匹配。
- 特殊情况处理:当滑动窗口内包含 n−1n - 1n−1 个石子且窗口长度为 n−1n - 1n−1 时,如果位置间隔允许,则只需 2 次移动完成。
- 结果输出:返回最终的最小移动次数。
结论
通过排序和滑动窗口算法,可以高效地求解石子最小移动次数问题。在每一步操作中,我们始终保持端点石子的位置,利用滑动窗口找到符合条件的连续位置窗口,最终得出最小移动次数。
复杂度分析:
- 排序操作的时间复杂度为 O(nlogn)O(n \log n)O(nlogn)。
- 滑动窗口遍历的时间复杂度为 O(n)O(n)O(n)。
因此,该算法的时间复杂度为 O(nlogn)O(n \log n)O(nlogn),对于一般情况非常高效。