旋转数组的最小数字——II
注:此题是昨天旋转数组的最小数字——I的拓展延伸,昨天题目数组的条件是不会存在重复元素,而本题数组的元素可以重复,因此建议先做前面一题,进行思考,这样求解这一题的过程就会容易理解许多。
旋转数组的性质
由旋转数组的最小数字——I我们知道了:
以数组最右侧数据
val
为参考对象,最小值右侧的数一定小于val
,最小值左边的数一定大于val
可以得到以下图像:
而今天这一题多了一个条件:数组中可能存在重复元素。我们需要考虑的情况又复杂了一点:
以数组最右侧数据
val
为参考对象,最小值右侧的数一定等于小于val
,最小值左边的数一定大于等于val
我们同样用二分法来解决:
-
同样,我们设左边界为
left
,右边界为right
,区间的中间下标为mid
-
如果
nums[mid] > nums[right]
,和原来一样,最小值一定不在左侧区域,left = mid + 1
-
如果
nums[mid] < nums[right]
,和原来一样,最小值一定不在右侧区域(但可能就是mid位置的值),right = mid
-
如果
nums[mid]==nums[rihgt]
,这种情况就值得注意了,如下图:
- 此时,
nums[mid]
可能在最小值的左边,也可能在最小值的右边,因此我们不能随意地舍去mid左侧的数据或者右侧的数据。对于这种情况的处理,我们有两种方法:
方法一:
不能随意舍去mid
左半部分或者右半部分是因为最小值到底是在mid
的左边还是右边是不确定的,但是如果我们可以通过处理来确定最小值和mid的关系呢?
我们知道,数组旋转后,其被分割成了两串非递减数列,而令我们困扰的就是诸如这种情况:[3,3,1,3],[4,4,5,1,4,4,4,4,4]
。那么我们可以删除数组前面所有等于nums[right]
的元素,使数组变成诸如[1,3],[5,1,4,4,4,4]
的形式,这样当nums[mid]==nums[right]
这种情况出现时,mid就一定会出现在最小值的右侧,mid右侧的数据也就可以删除了。
实现代码
int minArray(int* nums, int numsSize){
int left = 0;
int right = numsSize - 1;
//删除数组前面所有等于nums[right]的元素
while (left < numsSize && nums[left] == nums[right])
left++;
while (left < right)
{
int mid = (right - left) / 2 + left;
//如果中间值大于最右边的值,那么最小值一定在中间值的右边
if (nums[mid] > nums[right])
left = mid + 1;
//否则,最小值就在最右边的值的左边,也可能就是这个中间值
else
right = mid;
}
return nums[right];
}
方法二
虽然我们不能随意舍弃mid
的左半部分或者右半部分,但是我们可以确定,无论nums[right]
是不是最小值,都有它的一个替代品nums[mid]
,因此我们可以删除最右边的数据nums[right]
实现代码
int minArray(int* nums, int numsSize){
int left = 0;
int right = numsSize - 1;
while (left < right)
{
int mid = (right - left) / 2 + left;
//如果中间值大于最右边的值,那么最小值一定在中间值的右边
if (nums[mid] > nums[right])
left = mid + 1;
//如果中间值小于最右边的值,那么最小值一定在中间值的左边或者就是中间值
else if (nums[mid] < nums[right])
right = mid;
//如果中间值等于最右边的值,那么就删除最右侧元素
else
right--;
}
return nums[left];
}