石子移动问题

154 阅读3分钟

题解:最少和最多移动石子的次数

问题描述

给定一个整数数组 stones,表示在一排石子的位置。石子的位置是按照不同的数值表示的,而石子的目标是将所有石子移动到连续的位置上。我们需要实现两个功能:

  1. 最大移动次数:移动石子使得所有石子的位置成为一个连续的区间时,所需的最大步骤数。
  2. 最小移动次数:需要移动石子到连续位置的最小步骤数。

在此,我们将介绍如何通过排序和滑动窗口的方法来解决此问题。

解决方案

1. 最大移动次数的计算

要计算最大移动次数,需要确定可以获得的连续区间的长度。通过以下步骤,我们可以得到最大移动次数:

  • 排序石子的位置:这样可以方便地找到连续段。
  • 计算可能的最大移动距离:可以通过两种方式计算:
    • 移动石子位置 stones[1]stones[n-1] 的位置。
    • 移动石子位置 stones[0]stones[n-2] 的位置。
  • 求出两者中的最大值并减去总石子数量减去二(即 n-2)来得到最大移动次数。
int maxMoves = Math.max(stones[n - 1] - stones[1], stones[n - 2] - stones[0]) - (n - 2);
2. 最小移动次数的计算

对于最小移动次数,我们可以使用一个滑动窗口的方法:

  • 为每个石子 stones[i],我们要找到stones[i]stones[j]之间的窗口,其中在这个窗口内最大数量的石子应为 n - 1(即所有石子应该在这个区域内,或至少有一个缺失石子。)
  • 维护一个指向窗口右侧的指针 j,确保窗口内最多有 n 个石子。每次移动 j 时,如果符合条件增加 j
  • 对于每个窗口,计算当前的石子数量,若数量等于 n - 1 则可能需要特殊处理,设置最小移动次数为2。
  • 否则,根据当前窗口内的石子数量计算需要移动的数量。
while (j < n && stones[j] - stones[i] + 1 <= n) {
    j++;
}
// 计算当前窗口的石子数量
int alreadyInPlace = j - i;
// 更新最小移动次数
minMoves = Math.min(minMoves, n - alreadyInPlace);

最终实现

我们对以上逻辑进行了实现,最终的代码如下:

import java.util.Arrays;

public class Main {

    public static int solution(int[] stones) {
        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++;
            }
            // 计算在当前窗口内的石子数量
            int alreadyInPlace = j - i;
            // 特殊处理:窗内为 n-1 个石子
            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(n log n),而滑动窗口的遍历为 O(n),整体是高效的解决方案。