33. 搜索旋转排序数组
给你一个升序排列的整数数组 nums ,和一个整数 target 。
假设按照升序排序的数组在预先未知的某个点上进行了旋转。(例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请你在数组中搜索 target ,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
示例 1:
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
思路
利用二分法
题目要求时间复杂度 O(logn),显然应该使用二分查找。二分查找的过程就是不断收缩左右边界,而怎么缩小区间是关键。
如果数组[未旋转],在此数组中找一个特定的元素target 的过程
- 若target==nums[mid] 直接返回
- 若target<nums[mid],则target位于左侧区间[left,mid)中,经right=mid-1;在左侧区间查找
- 若target>nums[mid],则target位于左侧区间(left,mid]中,经left = mid+1在左侧区间查找
由于数组「被旋转」,所以左侧或者右侧区间不一定是连续的
当元素不重复时,如果 nums[i] <= nums[j],说明区间说明区间 [i,j]
是「连续递增」的。
i
、j
可以重合,所以这里使用的比较运算符是「小于等于」
因此,在旋转排序数组中查找一个特定元素时:
-
若 target == nums[mid],直接返回
-
若 nums[left] <= nums[mid],说明左侧区间 [left,mid]「连续递增」。此时:
-
若 nums[left] <= target <= nums[mid],说明 target 位于左侧。令 right = mid-1,在左侧区间查找
-
否则,令 left = mid+1,在右侧区间查找
-
-
否则,说明右侧区间 [mid,right]「连续递增」。此时:
-
若 nums[mid] <= target <= nums[right],说明 target 位于右侧区间。令 left = mid+1,在右侧区间查找
-
否则,令 right = mid-1,在左侧区间查找
注意:区间收缩时不包含 mid,也就是说,实际收缩后的区间是 [left,mid) 或者 (mid,right]
-
例图:
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
int left = 0;
int right = n - 1;
if (n == 0) {
return -1;
}
if (n == 1) {
return nums[0] == target ? 0 : -1;
}
while (left <= right) {
int mid = (left + right) >> 1;
// 等号:考虑 left==right,即只有一个元素的情况
if (nums[mid] == target) {
return mid;
}
// [left,mid] 连续递增
if (nums[left] <= nums[mid]) {
if (nums[left] <= target && target < nums[mid]) {
// 加等号,因为 left 可能是 target
right = mid - 1;
} else {
left = mid + 1;
}
} else {
// [mid,right] 连续递增
if (nums[mid] < target && target <= nums[n - 1]) {
// 加等号,因为 right 可能是 target
// 在右侧 (mid,right] 查找
left = mid + 1;
} else {
right = mid - 1;
}
}
}
System.out.println("未找到");
return -1;
}
}
81.搜索旋转排序数组-ii
搜索旋转排序数组 II
难度中等242
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,0,1,2,2,5,6]
可能变为 [2,5,6,0,0,1,2]
)。
编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true
,否则返回 false
。
示例 1:
输入: nums = [2,5,6,0,0,1,2], target = 0
输出: true
示例 2:
输入: nums = [2,5,6,0,0,1,2], target = 3
输出: false
道题是 33 题的升级版,元素可以重复。当 nums[left] == nums[mid] 时,无法判断 target 位于左侧还是右侧,此时无法缩小区间,退化为顺序查找。
left++,去掉一个干扰项,本质上还是顺序查找:
if nums[left] == nums[mid] {
left++
continue
}
代码
public boolean search(int[] nums, int target) {
int n = nums.length;
int left = 0;
int right = n - 1;
if (n == 0) {
return false;
}
if (n == 1) {
return nums[0] == target ? true : false;
}
while (left <= right) {
int mid = left + (right - left) >> 1;
if (nums[mid] == target) {
return true;
}
if (nums[left] == nums[mid]) {
left++;
continue;
}
//前半部分有序
if (nums[left] <= nums[mid]) {
//target在前半部分
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
//后半部分有序
//target在后半部分
if (nums[mid] < target && target <= nums[n - 1]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return false;
}
153. 寻找旋转排序数组中的最小值
难度中等293
假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7]
可能变为 [4,5,6,7,0,1,2]
。
请找出其中最小的元素。
示例 1:
输入:nums = [3,4,5,1,2]
输出:1
示例 2:
输入:nums = [4,5,6,7,0,1,2]
输出:0
方法一
class Solution {
public int findMin(int[] nums) {
int n=nums.length;
int left=0;
int right=n-1;
if (n==0){
return -1;
}
if (n==1){
return nums[0];
}
while (left<=right){// 实际上是不会跳出循环,当 left==right 时直接返回
if (nums[left]<=nums[right]){// 如果 [left,right] 递增,直接返回
return nums[left];
}
int mid=(left+right)>>1;
if (nums[left]<=nums[mid]){// [left,mid] 连续递增,则在 [mid+1,right] 查找
left=mid+1;
}else {
right=mid;// [left,mid] 不连续,在 [left,mid] 查找
}
}
return -1;
}
}
方法二
int left=0;
int right=nums.length-1;
while(right>left)
{
int mid=left+(right-left)/2;
if(nums[mid]>nums[right])
left=mid+1;
else
right=mid;
}
return nums[left];
154. 寻找旋转排序数组中的最小值 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
class Solution {
public int findMin(int[] nums) {
int n = nums.length;
int left = 0;
int right = n - 1;
if (n == 0) {
return -1;
}
if (n == 1) {
return nums[0];
}
while (left <= right) {
// 实际上是不会跳出循环,当 left==right 时直接返回
// 这里增加判断 ↓↓↓
if (nums[left] < nums[right] || left == right) {// 如果 [left,right] 递增,直接返回
return nums[left];
}
int mid =(right + left) >> 1;
if (nums[left] == nums[mid]) {
left++;// 无法判断 mid 位于哪一部分,去掉干扰项//有重复的值
}
else if (nums[left] < nums[mid]) {
// [left,mid] 连续递增,则在 [mid+1,right] 查找
left = mid + 1;
} else {
right = mid;
}
}
return -1;
}
}