一文解决面试高频的:搜索旋转排序数组问题

本文部分参考: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]连续递增。此时:
  1. nums[left] <= target <= nums[mid],说明 target 位于左侧。令right = mid-1,在左侧区间查找。
  2. 否则,令left = mid+1,在右侧区间查找
  • nums[left] >= nums[mid],说明右侧区间 [mid,right]连续递增。此时:
  1. nums[mid] <= target <= nums[right],说明 target 位于右侧区间。令left = mid+1,在右侧区间查找
  2. 否则,令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本深度学习、机器学习、自然语言处理、算法领域的经典电子书,我们将一次性统统分享给大家!

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值