原题地址:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/
题目描述:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
注意数组中可能存在重复的元素。
示例 1:
输入: [1,3,5]
输出: 1
示例 2:
输入: [2,2,2,0,1]
输出: 0
解题方案:
(前面都是废话,可以直接拉到底看最后一个代码ioi)
这道题刚开始写的时候,很多问题没有考虑到,导致后面越写越乱,写到中间甚至连二分查找都蒙了,主要没考虑到的问题有
1、没有考虑到前后两个子数组有相同值存在的情况
2、对于二分查找中mid与它前后值有没有超数组范围,以及right到底能不能小于left 一直没有想得太清楚,
首先若left<right,则mid最大也只能与left相等,所以mid+1不会超
其次,mid-1是可能超出的,因为mid可能为0,这种情况应该为left=0且right=1
在二分查找中right是可能小于left的,如果left与right相邻,则mid=left,若此时对应的数大于查找值,那么下个循环的right就小于left,而且在跳出循环后指向的值为left。
所以这道题虽然想到了用二分查找,但是如何应用在一开始并没有想明白。刚开始一直考虑的是nums[0]和nums[length-1]。把这两个数当成了前半数组的最小值和后半数组的最大值,一直用mid和这两个数作比较。第一次写出来的是只排除了两边数组首位的重复值,然后用中间值去作比较。
class Solution {
public int findMin(int[] nums) {
int l = nums.length;
if(l == 0)
{
return 0;
}
if(l == 1)
{
return nums[0];
}
if(nums[0] < nums[l - 1]) //没有旋转
{
return nums[0];
}
int left = 0, right = l - 1;
//去除首尾重复值
while(left + 1 <= l - 1 && nums[left] == nums[left + 1])
left ++;
while(right - 1 >= 0 && nums[right] == nums[right - 1])
right --;
//设置左右两数组的最小和最大值
int l_min = nums[left], r_max = nums[right];
int mid = (left + right) / 2;
while(left < right)
{
if(mid == 0)
{
//left为0,right为1的情况,此时分界点只能为1,不可能为0
return nums[1];
}
else //看分界点是在mid的前还是后
{
if(nums[mid - 1] > nums[mid])
return nums[mid];
if(nums[mid] > nums[mid + 1])
return nums[mid + 1];
//分界点在mid后
if(nums[mid] > l_min && nums[mid] > r_max)
{
left = mid + 1;
mid = (left + right) / 2;
}
//分界点在mid前
else if(nums[mid] < l_min && nums[mid] < r_max)
{
right = mid - 1;
mid = (left + right) / 2;
}
}
}
return nums[left];
}
}
但是这样做出来的复杂度很高,所以参考了一下别人的做法,发现不需要和最值作比较,只需要和left和right比较就可以了,因为分界点必定是在left和right之间的。
class Solution {
public int findMin(int[] nums) {
if(nums.length == 0)
{
return 0;
}
if(nums.length == 1 || nums[0] < nums[nums.length - 1])
{
return nums[0];
}
int left = 0, right = nums.length - 1;
int mid;
while(left < right)
{
while(left + 1 <= nums.length - 1 && nums[left] == nums[left + 1])
left ++;
while(right - 1 >= 0 && nums[right] == nums[right - 1])
right --;
mid = (left + right) / 2;
if(mid == 0)
{
return nums[1];
}
else
{
if(nums[mid - 1] > nums[mid])
return nums[mid];
if(nums[mid] > nums[mid + 1])
return nums[mid + 1];
if(nums[mid] > nums[left] && nums[mid] > nums[right])
{
left = mid + 1;
mid = (left + right) / 2;
}
else if(nums[mid] < nums[left] && nums[mid] < nums[right])
{
right = mid - 1;
mid = (left + right) / 2;
}
}
}
return nums[left];
}
}
这样做出来运行时间还不是最好的,然而排第一的方法用了遍历。。不过我跑了一次,时间和我的一样。
https://blog.csdn.net/qq_17550379/article/details/83689829
这是在网上找的答案,思路和方法都比我清晰多了,还是需要多加学习啊。。
又仔细地做了相关的题:
发现代码和153题完全相同,只不过当mid = high时可以理解为删除该重复值。
class Solution {
public int findMin(int[] nums) {
int low = 0, high = nums.length - 1, mid = 0;
while(low <= high)
{
mid = (low + high) / 2;
if(nums[mid] > nums[high])
low = mid + 1;
else if(nums[mid] < nums[high])
high = mid;
else
high --;
}
return nums[low];
}
}