【leetcode】旋转数组中的查找系列
旋转数组中的查找系列
这类题型主要是二分法的变形题,一般都是在二分法的代码上进行改进,可以分析 left mid right 这三个位置的相对大小情况来确定二分后两个区间内数据大小的分布情况来缩小范围进行二分。
我一般是先分析mid 和 right 的相对大小情况,如果还不能确定该怎么缩小范围,则继续分析 left 和 mid的相对大小情况。
注意:当数组中有重复元素的时候,如果nums[left] = nums[right] = nums[mid]
,也就是说这三个位置的元素大小相等的话,我们是没法唯一确左右定两个区间内部的数据大小分布,此时可以在该区间进行遍历,或者使用left++
或者right--
来缩小范围继续进行二分判断。
1. 旋转数组中的查找1
数组中没有重复元素。
例如[4,5,6,7,0,1,2]
,二分法的变形.
对于区间[left, right],每次二分的时候,一定能保证区间[left, mid]或者区间[mid, right]中,至少有一个区间是有序的。
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size()-1;
while(l<=r){
int mid = (l+r)/2;
if(nums[mid]==target) return mid;
if(nums[mid]<=nums[r]){// 右边一定有序
if(target>=nums[mid] && target<=nums[r]) l = mid+1;
else r = mid-1;
}
else{// 左边一定有序
if(target>=nums[l] && target<=nums[mid]) r = mid-1;
else l = mid+1;
}
}
return -1;
}
};
或者先判断左边:
class Solution {
public:
int search(vector<int>& nums, int target) {
int l = 0, r = nums.size()-1;
while(l<=r){
int mid = (l+r)/2;
if(target == nums[mid]) return mid;
if(nums[l]<=nums[mid]){// 左边一定有序
if(target>=nums[l] && target<=nums[mid]) r = mid-1;
else l = mid+1;
}
else{//右边一定有序
if(target>=nums[mid] && target<=nums[r]) l = mid+1;
else r = mid-1;
}
}
return -1;
}
};
2. 旋转数组中的查找2
数组中可能存在重复元素
仍然的思路和上一题类似,但是这里要注意重复元素的情况。我们考虑三个位置。left mid right
nums[mid] < nums[right]
此时能保证有部分是有序的nums[mid] > nums[right] 此时能保证左部分是有序的
nums[mid] = nums[right]
nums[l] < nums[mid]
左边有序nums[l] > nums[mid]
右边有序nums[l] = nums[mid]
无法确定两部分中哪部分是有序的,所以只能顺序遍历,或者说将left++
或者right--
都可以。
class Solution {
public:
bool search(vector<int>& nums, int target) {
// 旋转数组中的查找
int left = 0, right = nums.size()-1;
while(left<=right){
int mid = (left+right)/2;
if(target==nums[mid]) return true;
if(nums[mid]<nums[right] || (nums[mid]==nums[right]&&nums[left]>nums[mid])){// 右边有序
if(target>=nums[mid] && target<=nums[right]) left = mid+1;
else right = mid-1;
}
else if(nums[mid]>nums[right] || (nums[mid]==nums[right]&&nums[left]<nums[mid])){//左边有序
if(target>=nums[left] && target<=nums[mid]) right = mid-1;
else left = mid+1;
}
else{//无法确定 只能遍历 或者left++ 或者 right--
// for(int i=left; i<=right; i++){
// if(nums[i]==target) return true;
// }
// return false;
// 上面注释的是遍历 可以left++ 或者right--
left++;//right--也一样
}
}
return false;
}
};
3. 旋转数组中的最小值1
没有重复元素。
还是考虑三个位置 left mid right
nums[mid] <= nums[right]
此时右边一定是有序的,最小值可能在左边,或者刚好mid位置nums[mid] > nums[right]
此时左边一定是有序的,最小值一定在右边
class Solution {
public:
int findMin(vector<int>& nums) {
// 旋转数组中没有重复元素 找出最小值
int left = 0, right = nums.size()-1;
while(left<=right){
if(left==right) return nums[left];// 当left和right重合 该位置就是最小的数
int mid = (left+right)/2;
if(nums[mid] <= nums[right]) right = mid;// 右边一定有序,最小值要么在mid位置,要么在前一半
else left = mid+1;// nums[mid]>nums[right] 左边一定有序 最小值一定在右部分
}
return -1;
}
};
4. 旋转数组中的最小值2
数组中有重复元素。
还是一样的做法,针对三个位置数据的大小进行讨论。left mid right
nums[mid] < nums[right]
最小值在左边或者mid位置nums[mid] > nums[right]
最小值在右边,不包括mid位置nums[mid] = nums[right]
nums[l] < nums[mid]
数组一定是有序的,最小值是nums[l]
nums[l] > nums[mid]
左边或者mid位置nums[l] = nums[mid]
三个位置的大小都是一样的,无法确定在那部分,可以选择在该区间内遍历,h或者left++
,或者right--
class Solution {
public:
int findMin(vector<int>& nums) {
// 旋转数组中的最小值,可能包含重复元素
int left = 0, right = nums.size()-1;
while(left<=right){
if(left==right) return nums[left];
int mid = (left+right)/2;
if((nums[mid]<nums[right]) || (nums[mid]==nums[right]&&nums[left]>nums[mid])) right = mid;
else if(nums[mid] > nums[right]) left = mid+1;
else if(nums[left]<nums[mid]) return nums[left];
else{//遍历 或者直接right-- 或者left++
// int cur_min = INT_MAX;
// for(int i=left; i<=right; i++){
// cur_min = min(nums[i], cur_min);
// }
// return cur_min;
left++;//或者right--都一样
}
}
return -1;
}
};