伴学笔记4

214 阅读5分钟

石子移动问题

问题描述

小S正在玩一个关于石子的游戏,给定了一些石子,它们位于一维数轴的不同位置,位置用数组 stones 表示。如果某个石子处于最小或最大的一个位置,我们称其为端点石子

在每个回合,小S可以将一颗端点石子移动到一个未占用的位置,使其不再是端点石子。游戏继续,直到石子的位置变得连续,无法再进行任何移动操作。

你需要帮助小S找到可以移动的最大次数。


测试样例

样例1:

输入:stones = [7, 4, 9]
输出:2

样例2:

输入:stones = [6, 5, 4, 3, 10]
输出:3

样例3:

输入:stones = [1, 2, 3, 4, 5]
输出:0

问题理解

有一个数组 stones,表示石子在一维数轴上的位置。你需要计算出将这些石子移动到连续位置所需的最大移动次数。每次移动只能将端点石子(即位置最小或最大的石子)移动到一个未占用的位置。

关键点

  1. 端点石子:每次只能移动位置最小或最大的石子。
  2. 连续位置:最终目标是使所有石子占据连续的位置。

解题思路

  1. 排序:首先对 stones 数组进行排序,这样可以更容易地处理石子的位置关系。

  2. 计算最大移动次数

    • 最大移动次数可以通过计算当前石子位置与理想连续位置之间的差距来确定。
    • 理想情况下,石子应该占据 [min(stones), min(stones) + n - 1] 这个区间内的所有位置,其中 n 是石子的数量。
    • 计算当前石子位置与理想位置之间的差距,并找出最大差距。
  3. 滑动窗口

    • 使用滑动窗口来计算最小移动次数。滑动窗口可以帮助我们找到一个连续的子数组,使得其中的石子数量尽可能多。
    • 通过滑动窗口,我们可以计算出需要移动的最小石子数量,使得所有石子连续。

算法步骤

  1. 排序:对 stones 数组进行排序。

  2. 计算最大移动次数

    • 计算理想位置与当前位置之间的最大差距。
  3. 滑动窗口

    • 使用滑动窗口来计算最小移动次数。

使用滑动窗口来计算最小移动次数的原因在于,滑动窗口可以帮助我们有效地找到一个连续的子数组,使得其中的石子数量尽可能多。通过这种方式,我们可以计算出需要移动的最小石子数量,使得所有石子连续。

详细解释

  1. 连续子数组

    • 我们需要找到一个连续的子数组,使得其中的石子数量尽可能多。这样,我们就可以计算出需要移动的最小石子数量,使得所有石子连续。
    • 滑动窗口可以帮助我们动态地调整子数组的范围,从而找到最优的连续子数组。
  2. 滑动窗口的优势

    • 动态调整:滑动窗口可以在不重新计算整个子数组的情况下,动态地调整窗口的范围。这使得我们可以在 O(n) 的时间复杂度内找到最优的连续子数组。
    • 高效计算:通过滑动窗口,我们可以高效地计算出需要移动的最小石子数量。具体来说,我们可以通过窗口的起始位置和结束位置来计算当前窗口内的石子数量,并根据这个数量来调整窗口的范围。

具体步骤

  1. 初始化窗口

    • 初始化窗口的起始位置 i 和结束位置 j
  2. 扩展窗口

    • 不断扩展窗口的结束位置 j,直到窗口内的石子数量达到 n 或者窗口内的石子位置不再连续。
  3. 调整窗口

    • 如果窗口内的石子数量达到 n,则计算当前窗口内的最小移动次数,并更新最小移动次数。
    • 如果窗口内的石子位置不再连续,则移动窗口的起始位置 i,直到窗口内的石子位置再次连续。

通过这种方式,滑动窗口可以帮助我们高效地找到最优的连续子数组,并计算出需要移动的最小石子数量。

Java代码

ini
 代码解读
复制代码
import java.util.Arrays;

public class Main {
    public static int solution(int[] stones) {
        // write code here
        if (stones.length == 1) {
            return 0;
        }
        Arrays.sort(stones);
        int n = stones.length;

        // 计算最大移动次数
        int maxMoves = Math.max(stones[n - 1] - stones[1], stones[n - 2] - stones[0]) - (n - 2);

        // 计算最小移动次数,使用滑动窗口
        int minMoves = Integer.MAX_VALUE;
        int j = 0;
        for (int i = 0; i < n; i++) {
            // 确保窗口内最多有 n 个石子
            while (j < n && stones[j] - stones[i] + 1 <= n) {
                j++;
            }
            // 如果窗口内有 n - 1 个石子,且空位为 1,则需要特殊处理
            int alreadyInPlace = j - i;
            if (alreadyInPlace == n - 1 && stones[j - 1] - stones[i] + 1 == n - 1) {
                minMoves = Math.min(minMoves, 2);
            } else {
                minMoves = Math.min(minMoves, n - alreadyInPlace);
            }
        }

        return maxMoves;
    }

    public static void main(String[] args) {
        System.out.println(solution(new int[]{7, 4, 9}) == 2);
        System.out.println(solution(new int[]{6, 5, 4, 3, 10}) == 3);
        System.out.println(solution(new int[]{1, 2, 3, 4, 5}) == 0);
    }
}

复杂度分析

时间复杂度:排序的时间复杂度为 (O(nlogn))。计算最大移动次数和最小移动次数的过程是线性的,即(O(n))。因此,总的时间复杂度为 (O(nlogn))。

空间复杂度:主要使用了排序所需的额外空间,空间复杂度为 (O(1))(如果不考虑排序的空间复杂度)。 - 总的空间复杂度为 (O(1))(不考虑输入数组的空间)。