难度中等196
在一个长度 无限 的数轴上,第 i
颗石子的位置为 stones[i]
。如果一颗石子的位置最小/最大,那么该石子被称作 端点石子 。
每个回合,你可以将一颗端点石子拿起并移动到一个未占用的位置,使得该石子不再是一颗端点石子。
值得注意的是,如果石子像 stones = [1,2,5]
这样,你将 无法 移动位于位置 5 的端点石子,因为无论将它移动到任何位置(例如 0 或 3),该石子都仍然会是端点石子。
当你无法进行任何移动时,即,这些石子的位置连续时,游戏结束。
要使游戏结束,你可以执行的最小和最大移动次数分别是多少? 以长度为 2 的数组形式返回答案:answer = [minimum_moves, maximum_moves]
。
示例 1:
输入:[7,4,9] 输出:[1,2] 解释: 我们可以移动一次,4 -> 8,游戏结束。 或者,我们可以移动两次 9 -> 5,4 -> 6,游戏结束。
示例 2:
输入:[6,5,4,3,10] 输出:[2,3] 解释: 我们可以移动 3 -> 8,接着是 10 -> 7,游戏结束。 或者,我们可以移动 3 -> 7, 4 -> 8, 5 -> 9,游戏结束。 注意,我们无法进行 10 -> 2 这样的移动来结束游戏,因为这是不合要求的移动。
示例 3:
输入:[100,101,104,102,103] 输出:[0,0]
提示:
3 <= stones.length <= 10^4
1 <= stones[i] <= 10^9
stones[i]
的值各不相同。
这个题目标记中等题,实际emmmm
-
贪心思想
首先,考虑一个特殊情况,当石头排列连续时,最小次数为 0
如果不连续,假设当前端点石子是 left 和 right。考虑将 left 移动到 right + 1 的位置,或者将 right 移动到 left - 1 的位置,这样就会使得 left 和 right 不再是端点石子,而新的端点石子会是 left - 1 或 right + 1 中的一个。
所以考虑下一步究竟移动哪一侧的端点石子???
为了避免出现无法移动的石子,我们可以通过计算两个端点石子和中位数的距离来判断哪个更优
具体如下:
左端点 left 和右端点 right 之间的长度为 len = right - left + 1。
接下来找到中位数 mid,
当 len 是奇数时,mid 即为 stones[(left+right)/2]
当 len 是偶数时,mid 不能取平均值,应该选择靠左的那个中间值,即 mid = stones[(left+right)/2]
如果 mid 与 left 的距离更大,说明移动左端点更优。
如果 mid 与 right 的距离更大,说明移动右端点更优。
如果 mid 与 left、right 的距离相等,我们可以移动左端点或右端点
计算最小和最大次数时,我们只需要每次移动一个端点石子,直到无法继续移动为止。
对于最小次数,只需记录移动次数即可
对于最大次数,每移动一次就将记录值加 1
-
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int min(int a, int b) {
return a < b ? a : b;
}
int max(int a, int b) {
return a > b ? a : b;
}
int cmp(const void *a, const void *b) {
return *(int*)a - *(int*)b;
}
int minMoves(int* stones, int stonesSize) {
if (stonesSize == 2) {
return 0;
}
qsort(stones, stonesSize, sizeof(int), cmp);
int left = 0, right = stonesSize - 1;
int mid = (stones[left] + stones[right]) / 2;
int minMoves = INT_MAX;
while (left < right) {
if (left + 1 == right && stones[right] - stones[left] == 2) {
break;
}
mid = (stones[left] + stones[right]) / 2;
if (mid == stones[left]) {
minMoves = min(minMoves, right - left - 1 + stones[right] - mid);
break;
} else if (mid == stones[right]) {
minMoves = min(minMoves, right - left - 1 + mid - stones[left]);
break;
} else if (mid - stones[left] > stones[right] - mid) {
minMoves = min(minMoves, right - left - 1 + mid - stones[left]);
left++;
} else if (mid - stones[left] < stones[right] - mid) {
minMoves = min(minMoves, right - left - 1 + stones[right] - mid);
right--;
} else {
int movesIfMoveLeft = right - left - 1 + mid - stones[left];
int movesIfMoveRight = right - left - 1 + stones[right] - mid;
minMoves = min(minMoves, min(movesIfMoveLeft, movesIfMoveRight));
left++;
right--;
}
}
return minMoves;
}
int maxMoves(int* stones, int stonesSize) {
if (stonesSize == 2) {
return 0;
}
qsort(stones, stonesSize, sizeof(int), cmp);
int left = 0, right = stonesSize - 1;
int mid = (stones[left] + stones[right]) / 2;
int maxMoves = 0;
while (left < right) {
mid = (stones[left] + stones[right]) / 2;
if (mid == stones[left]) {
maxMoves += right - left - 1 + stones[right] - mid;
break;
} else if (mid == stones[right]) {
maxMoves += right - left - 1 + mid - stones[left];
break;
} else if (mid - stones[left] > stones[right] - mid) {
maxMoves += right - left - 1 + mid - stones[left];
left++;
} else if (mid - stones[left] < stones[right] - mid) {
maxMoves += right - left - 1 + stones[right] - mid;
right--;
} else {
int movesIfMoveLeft = right - left - 1 + mid - stones[left];
int movesIfMoveRight = right - left - 1 + stones[right] - mid;
maxMoves += max(movesIfMoveLeft, movesIfMoveRight);
left++;
right--;
}
}
return maxMoves;
}
int main() {
int stones[] = {0, 1, 3, 5, 6, 8, 12, 17};
int stonesSize = sizeof(stones) / sizeof(stones[0]);
printf("minMoves: %d\n", minMoves(stones, stonesSize));
printf("maxMoves: %d\n", maxMoves(stones, stonesSize));
return 0;
}
-
力扣执行代码:
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
int min(int a, int b) {
return a < b ? a : b;
}
int max(int a, int b) {
return a > b ? a : b;
}
int cmp(const void *a, const void *b) {
return *(int*)a - *(int*)b;
}
int* numMovesStonesII(int* stones, int stonesSize, int* returnSize){
qsort(stones, stonesSize, sizeof(int), cmp);
int maxMoves = max(stones[stonesSize-1] - stones[1] - stonesSize + 2, stones[stonesSize-2] - stones[0] - stonesSize + 2);
int minMoves = INT_MAX, i = 0, j = 0;
for (i = 0; i < stonesSize; i++) {
while (j + 1 < stonesSize && stones[j + 1] - stones[i] < stonesSize) {
j++;
}
int cost = stonesSize - (j - i + 1);
if (j - i + 1 == stonesSize - 1 && stones[j] - stones[i] + 1 == stonesSize - 1) {
cost = 2;
}
minMoves = min(minMoves, cost);
}
int *res = (int*)malloc(sizeof(int)*2);
res[0] = minMoves;
res[1] = maxMoves;
*returnSize = 2;
return res;
}
numMovesStonesII
函数为主要函数,接受一个整数数组 stones
和它的大小 stonesSize
,返回一个动态分配的整数数组。
//当然这个是力扣的板子,必须遵守其规则
函数首先对输入的石头位置进行排序
对于最大步数,
可以初步估计答案为将除第一块和最后一块石头外的所有石头堆在一起所需移动的最小步数
需要移动的步数为
stones[stonesSize-1] - stones[1] - stonesSize + 2
或
stones[stonesSize-2] - stones[0] - stonesSize + 2
取MAX
对于最小步数,
在有序石头数组中找到长度为 stonesSize
的连续子序列,
只要连续子序列的长度小于 stonesSize-1
,就不断右移右端点直至恰好等于 stonesSize-1
,同时记录需要移动的石头数。
当连续子序列刚好缺失一块石头时,需要移动两次才能将这块石头移到连续子序列中,因此需要特判
最后,将计算出的两个最大值和最小值填入动态分配的整数数组中,设置 returnSize
的值为 2,并返回该数组。