青训营X豆包MarsCode 技术训练营第九课 | 豆包MarsCode AI 刷题

67 阅读3分钟

第480题 端点石子移动问题

题目分析

给定一个非递减排序的数组 A,需要判断是否存在两个不同的、长度相同的子数组,它们的元素总和相等。子数组可题目描述了一种石子排列的游戏,给出了一组石子在一维数轴上的不同位置,目标是通过最少的移动次数,使这些石子的位置连续。每次移动可以将端点石子移动到未占用的位置,但不能移到当前其他石子的位置。游戏在石子排列成连续位置时结束,因此我们需要计算最少的移动次数。

题目给出的测试样例让我们观察到:

  1. 当石子的位置本来就连续时,无需移动,移动次数为 0。
  2. 当石子的位置分布较为零散时,需要通过移动端点石子来使它们变得连续

解题思路

  • 排序:首先对石子的位置数组进行排序,使石子的位置按照从小到大的顺序排列。这样可以方便地找到连续窗口。

  • 滑动窗口:由于石子数量是固定的,我们可以使用一个长度为 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)

模块解释

代码分为以下几个模块:

  1. 排序模块stones.sort(),对输入石子位置排序,确保后续滑动窗口操作可以顺利进行。
  2. 滑动窗口模块:通过双指针构建滑动窗口,滑动过程中每次统计窗口内的连续石子数。窗口移动到一个新位置后,检查窗口内的石子数量与连续的需求是否匹配。
  3. 特殊情况处理:当滑动窗口内包含 n−1n - 1n−1 个石子且窗口长度为 n−1n - 1n−1 时,如果位置间隔允许,则只需 2 次移动完成。
  4. 结果输出:返回最终的最小移动次数。

结论

通过排序和滑动窗口算法,可以高效地求解石子最小移动次数问题。在每一步操作中,我们始终保持端点石子的位置,利用滑动窗口找到符合条件的连续位置窗口,最终得出最小移动次数。

复杂度分析

  • 排序操作的时间复杂度为 O(nlog⁡n)O(n \log n)O(nlogn)。
  • 滑动窗口遍历的时间复杂度为 O(n)O(n)O(n)。

因此,该算法的时间复杂度为 O(nlog⁡n)O(n \log n)O(nlogn),对于一般情况非常高效。