目录

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​​ 可以重合,所以这里使用的比较运算符是「小于等于」

因此,在旋转排序数组中查找一个特定元素时:

  1. 若 target == nums[mid],直接返回
  2. 若 nums[left] <= nums[mid],说明左侧区间 [left,mid]「连续递增」。此时:
  • 若 nums[left] <= target <= nums[mid],说明 target 位于左侧。令 right = mid-1,在左侧区间查找
  • 否则,令 left = mid+1,在右侧区间查找
  1. 否则,说明右侧区间 [mid,right]「连续递增」。此时:
  • 若 nums[mid] <= target <= nums[right],说明 target 位于右侧区间。令 left = mid+1,在右侧区间查找
  • 否则,令 right = mid-1,在左侧区间查找
    注意:区间收缩时不包含 mid,也就是说,实际收缩后的区间是 [left,mid) 或者 (mid,right]

例图:

LC033-LC081-LC153-LC154--搜索旋转排序数组(二分查找)_二分法

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;
}
}