本文部分参考:https://imageslr.github.io/2020/03/06/leetcode-33.html#%E6%80%BB%E7%BB%93
今天带来搜索旋转排序数组题的总结,:
- LeetCode 33 题:搜索旋转排序数组
- LeetCode 81 题:搜索旋转排序数组-ii
- LeetCode 153 题:寻找旋转排序数组中的最小值
- LeetCode 154 题:寻找旋转排序数组中的最小值-ii
这几道题可以分为三类:33和81题属于搜索特定值,153和154题属于搜索最小值,81和154题属于包含重复元素。
33. 搜索旋转排序数组
给你一个升序排列的整数数组nums(元素不重复),和一个整数target。
假设按照升序排序的数组在预先未知的某个点上进行了旋转。(例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1
【题解】
题目要求时间复杂度O(logn),显然应该使用二分查找。二分查找的过程就是不断收缩左右边界,而怎么缩小区间是关键。
如果数组未旋转,在数组中查找一个特定元素 target 的过程为:
- 若
target == nums[mid],直接返回 - 若
target < nums[mid],则 target 位于左侧区间 [left,mid) 中。令right = mid-1,在左侧区间查找 - 若
target > nums[mid],则 target 位于右侧区间 (mid,right] 中。令left = mid+1,在右侧区间查找
但是这道题,由于数组被旋转,所以左侧或者右侧区间不一定是连续的。在这种情况下,如何判断 target 位于哪个区间?
根据旋转数组的特性,当元素不重复时,如果 nums[i] <= nums[j],说明区间 [i,j] 连续递增的。
因此,在旋转排序数组中查找一个特定元素时:
- 若
target == nums[mid],直接返回 - 若
nums[left] <= nums[mid],说明左侧区间 [left,mid]连续递增。此时:
- 若
nums[left] <= target <= nums[mid],说明 target 位于左侧。令right = mid-1,在左侧区间查找。 - 否则,令
left = mid+1,在右侧区间查找
- 若
nums[left] >= nums[mid],说明右侧区间 [mid,right]连续递增。此时:
- 若
nums[mid] <= target <= nums[right],说明 target 位于右侧区间。令left = mid+1,在右侧区间查找 - 否则,令
right = mid-1,在左侧区间查找
【代码】
class Solution {
public int search(int[] nums, int target) {
if(nums.length == 0)
return -1;
int left = 0;
int right = nums.length-1;
while(left <= right) {
int mid = (left + right) / 2;
if(nums[mid] == target)
return mid;
if(nums[left] <= nums[mid]){ //[left,mid] 连续递增
if(nums[left] <= target && target <= nums[mid])
right = mid - 1; //在左侧 [left,mid) 查找
else
left = mid + 1;
} else { // [mid,right] 连续递增
if(nums[mid] <= target && target <= nums[right])
left = mid + 1; //在右侧 (mid,right] 查找
else
right = mid - 1;
}
}
return -1;
}
}
81. 搜索旋转排序数组-ii
假设按照升序排序的数组(元素可能重复)在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6] 可能变为 [2,5,6,0,0,1,2] )。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。
【题解】
这道题是33题的升级版,元素可以重复。当nums[left] == nums[mid]时,无法判断 target 位于左侧还是右侧,此时无法缩小区间,退化为顺序查找。但是,在部分情况下还是可以使用二分查找来优化的。
顺序查找的代码如下,在leetcode上已经能取到一个比较高的排名:
class Solution {
public boolean search(int[] nums, int target) {
if(nums.length==0)
return false;
for(int i=0;i<nums.length;i++){
if(nums[i] == target)
return true;
}
return false;
}
}
在部分情况下使用二分查找来优化的改进版本如下:
class Solution {
public boolean search(int[] nums, int target) {
if (nums == null || nums.length == 0)
return false;
int start = 0;
int end = nums.length - 1;
while (start <= end) {
int mid = (end + start) / 2;
if (nums[mid] == target)
return true;
if (nums[start] == nums[mid]) {
start++;
continue;
}
//前半部分有序
if (nums[start] < nums[mid]) {
//target在前半部分
if (nums[mid] > target && nums[start] <= target) {
end = mid - 1;
} else { //否则,去后半部分找
start = mid + 1;
}
} else {
//后半部分有序
//target在后半部分
if (nums[mid] < target && nums[end] >= target) {
start = mid + 1;
} else { //否则,去后半部分找
end = mid - 1;
}
}
}
//一直没找到,返回false
return false;
}
}
【代码】
class Solution {
public int search(int[] nums, int target) {
if(nums.length == 0)
return -1;
int left = 0;
int right = nums.length-1;
while(left <= right) {
int mid = (left + right) / 2;
if(nums[mid] == target)
return mid;
if(nums[left] <= nums[mid]){ //[left,mid] 连续递增
if(nums[left] <= target && target <= nums[mid])
right = mid - 1; //在左侧 [left,mid) 查找
else
left = mid + 1;
} else { // [mid,right] 连续递增
if(nums[mid] <= target && target <= nums[right])
left = mid + 1; //在右侧 (mid,right] 查找
else
right = mid - 1;
}
}
return -1;
}
}
aaaaaaa
153. 寻找旋转排序数组中的最小值
假设按照升序排序的数组(元素不重复)在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
【题解】
如果数组没有翻转,即nums[left] <= nums[right],则nums[left]就是最小值,直接返回。
如果数组翻转,需要找到数组中第二部分的第一个元素:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XugiI7YE-1605613180714)(https://imgkr2.cn-bj.ufileos.com/0de23889-99f3-4740-ab38-eb461fd9fd13.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=oIpiDsnPIf8%252FpsiRBiY0RQxs784%253D&Expires=1605697772)]
若nums[left] <= nums[mid],说明区间 [left,mid] 连续递增,则最小元素一定不在这个区间里,可以直接排除。因此,令left = mid+1,在 [mid+1,right] 继续查找
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iJTTcPL9-1605613180716)(https://imgkr2.cn-bj.ufileos.com/64fdd0ff-f542-485f-9524-904f09f3ad43.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=0ajwbEFg4SEQUR8HIOJXKbVM5oE%253D&Expires=1605697811)]
否则,说明区间 [left,mid] 不连续,则最小元素一定在这个区间里。因此,令right = mid,在 [left,mid] 继续查找
要注意的是,[left,right] 表示当前搜索的区间。注意 right 更新时会被设为 mid 而不是 mid-1,因为 mid 无法被排除。这一点和33题是不同的
【代码】
class Solution {
public int findMin(int[] nums) {
int start = 0;
int end = nums.length-1;
while(start < end){
int mid = (start+end)/2;
if((nums[mid-1] > nums[mid]) && (nums[mid] < nums[mid+1]))
return nums[mid];
if((nums[start] > nums[mid]) && (nums[mid] > nums[end]))
return nums[end]
if((nums[start] > nums[mid]) && (nums[mid] < nums[end]))
start = mid + 1;
}
return -1;
}
}
本题代码还可以化简如下,思想是一样的:
class Solution {
public int findMin(int[] nums) {
int n = nums.length;
int left = 0,right = n-1;
while(left < right){
int mid = (left + right) / 2;
if(nums[mid] > nums[right])
left = mid + 1;
else
right = mid;
}
return nums[right];
}
}
154. 寻找旋转排序数组中的最小值-ii
假设按照升序排序的数组(有重复元素)在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 。
请找出其中最小的元素。
【题解】
这道题是 153 题的升级版,元素可以重复。和 81 题一样,当 nums[left] == nums[mid] 时,退化为顺序查找。那我们也是在部分情况下优化代码,如下所示:
【代码】
class Solution {
public int findMin(int[] nums) {
int low = 0;
int high = nums.length - 1;
while (low < high) {
int mid = (high + low) / 2;
if (nums[mid] < nums[high]) {
high = mid;
} else if (nums[mid] > nums[high]) {
low = mid + 1;
} else {
high -= 1;
}
}
return nums[low];
}
}
微信扫码关注公众号,后台回复「电子书福利」,35本深度学习、机器学习、自然语言处理、算法领域的经典电子书,我们将一次性统统分享给大家!





4万+

被折叠的 条评论
为什么被折叠?



