问题描述
小S正在玩一个关于石子的游戏,给定了一些石子,它们位于一维数轴的不同位置,位置用数组 stones 表示。如果某个石子处于最小或最大的一个位置,我们称其为端点石子。
在每个回合,小S可以将一颗端点石子移动到一个未占用的位置,使其不再是端点石子。游戏继续,直到石子的位置变得连续,无法再进行任何移动操作。
你需要帮助小S找到可以移动的最大次数。
思路
0、约定
为方便编写文章,做出如下约定:
1、对于排序后stones数组中的相邻石子之间的空闲位置的个数,在后续的文章中统一称为“间隙” ,文中表示为数组gap(差分数组)
“间隙”计算方式为:
其中 长度为
1、审题:
根据题意,我们每次需要将排序后数组两端的其中一个石子插入到数组中的空闲位置,并且一次操作之后,这个石子不能在数组边缘。
2、排序:
为了方便观察以及找出端点石子,我们首先对数组进行排序
用例1:
输入:
stones = [4, 7, 9]输出:2
用例2:
输入:
stones = [0, 3, 6, 8, 9]输出:5
3、观察用例:
对于用例1stones = [4, 7, 9]:
石子间“间隙”(空闲位置个数)为gap = [2, 1]
如果我们将4移动到位置8,变成[7, 8, 9],只能移动1次,而如果将9移动到5或6,则可以得到[4, 5, 7]或[4, 6, 7],再次将4移动到6或者7移动到 5,可形成[5, 6, 7]或[4, 5, 6],可以移动2次.
由此可得:对于
stones左右边界与它们相邻元素的“间隙”(差值-1),我们应当尽量选择较大的一个,如Math.max(7-4, 9-7)-1,因为当我们选择了一个端点石子移动之后,被选择的端点石子与相邻石子的“间隙”就会消失,我们就无法再得到这个间隙的操作次数
对于用例2stones = [0, 3, 6, 8, 9]:
石子间“间隙”(空闲位置个数)为gap = [2, 2, 1, 0]
由用例1可得,两端我们优先选择“间隙”小的一边移动,假设总次数为res,则第一次移动后stones = [0, 1, 3, 6, 8],间隙为gap = [0, 1, 2, 1],res = 1。
tips: 为了不让“间隙”丢失,我们尽量将石子移动到
1而不是2,假设我们移动到2,就会变成stones = [0, 2, 3, 6, 8],gap = [1, 0, 2, 1],这样我们无论如何都会丢失1
根据上述逻辑,我们可以继续往下模拟,得到如下结果:
0移动到7:
stones = [1, 3, 6, 7, 8],gap = [1, 2, 0, 0],res = 28移动到2:
stones = [1, 2, 3, 6, 7],gap = [0, 0, 2, 0],res = 37移动到5(或4):
stones = [1, 2, 3, 5, 6],gap = [0, 0, 1, 0],res = 41(或6)移动到4:
stones = [2, 3, 4, 5, 6],gap = [0, 0, 0, 0],res = 5
观察上面的逻辑,我们可以得到如下结论:
1、优先选择间隙较小的端点石子移动,如果间隙相同,移动任意一个端点石子都行。
2、只要存在至少一个端点石子的间隙为0,则剩余的间隙次数都能拿到。
综上,我们可以简化为:对数组排序后,计算相邻石子的间隙,对于非端点石子,直接记录到结果,对于两个端点石子,取最大值。
代码
public static int solution(int[] stones) {
// 数量小于3时,无法移动任一端点石子
if (stones.length < 3) return 0;
// 排序
Arrays.sort(stones);
int res = 0;
// 记录左右端点,方便取最值
int l = 0, r = 0;
for (int i = 1; i < stones.length; i++) {
// 左端点
if (i == 1) l = stones[i] - stones[i - 1] - 1;
// 右端点
else if (i == stones.length - 1) r = stones[i] - stones[i - 1] - 1;
// 非端点,直接统计
else res += stones[i] - stones[i - 1] - 1;
}
// 返回结果
return res + Math.max(l, r);
}
复杂度
时间复杂度: 排序为,遍历一次数组为,总体复杂度可以视作
空间复杂度:(未计算排序的复杂度)