154. 寻找旋转排序数组中的最小值 II
有两种解法,第一种是暴力法,依次比较遇到的元素和下一个元素,但这样在最坏的情况下会浪费时间(最小的元素在最后),时间复杂度会超。
另一种解法是采用二分法。
对于能够使用二分法的题,一般来说会根据下面两点来采用:
- 顺序存储(能够通过下标获取关键字)
- 元素有序(能够通过任意关键字的值来确定所找关键字的位置)
虽然此题在表面上没有满足元素有序,但是实际上还是能够通过任意关键字的值来确定所找关键字的位置,原因在于旋转的是一部分,这也就能够保证部分有序。利用二分法进行nums[mid]与nums[right]的比较,从而不断缩小查找范围,会有三种情况:
- nums[mid] < nums[right]。此时,最小值一定在左侧,且nums[mid]有可能是最小值,故在这种情况下,缩小范围时,要让right = mid,而不是right = mid - 1。
- nums[mid] > nums[right]。此时,最小值一定在右侧,且nums[mid]不可能是最小值,故在这种情况下,缩小范围时,left = mid + 1。
- nums[mid] == nums[right]。此时说明数组中有重复的元素,这时需要用一种方法来缩小范围:暴力法,也就是令right--。这样并不会影响结果,原因在于存在与nums[right]相同的值,不需要担心会漏掉最小值。
代码如下:
int findMin(int* nums, int numsSize){
int left,right,mid;
left = 0; right = numsSize - 1;
while(left < right){
mid = left + (right - left) / 2;
if(nums[mid] > nums[right])//条件满足则说明最小值一定在右侧,且nums[mid]一定不是最小值
left = mid + 1;
else
if(nums[mid] < nums[right])//条件满足则说明最小值一定在左侧,且nums[mid]有可能是最小值
right = mid; //这也就是为什么不是right = mid - 1的原因
else//数字中存在相同的元素
right--;
}
return nums[left];
}
540. 有序数组中的单一元素
同样也是采用二分法,在这之前,必须搞懂如何二分及二分的规则:
由题目可以知道,数组有序且仅有一个元素只会出现一次,其它元素均会出现两次,我们记这个只出现一次的元素为x,观察下标可以得到如下规则:
- 假设不存在x,则下标为偶数的元素(设下标为mid)一定与下标为mid+1的元素相等,下标为奇数的元素(设为mid)一定与下标为mid-1的元素相等,我们记这样的规则为初始规则
- 而有了x之后,x左侧的元素仍然满足此规则,而x右侧的元素满足相反的规则,我们记这样的规则为变化规则
由此,可以写出代码:
int singleNonDuplicate(int* nums, int numsSize){
int left,right,mid;
left = 0; right = numsSize - 1;
while(left < right){
mid = left + (right - left) / 2;
if(mid % 2 == 0){//当下标为偶数时
if(nums[mid] == nums[mid + 1])//若初始规则成立,说明单元素在mid右侧,并未影响左侧元素的初始规则
left = mid + 1;
else//如果规则不成立,说明是因为有单元素的插入导致的规则改变,且nums[mid]也有可能是单元素
right = mid;
}
else{//奇数下标的情况相同
if(nums[mid] == nums[mid - 1])
left = mid + 1;
else
right = mid;
}
}
return nums[left];
}