daily leetcode - search-in-rotated-sorted-array - !

题目地址

https://leetcode.com/problems/search-in-rotated-sorted-array/

题目描述

Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand.

(i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

You are given a target value to search. If found in the array return its index, otherwise return -1.

You may assume no duplicate exists in the array.

Your algorithm's runtime complexity must be in the order of O (log n ).

Example 1:

Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4

Example 2:

Input: nums = [4,5,6,7,0,1,2], target = 3
Output: -1

思路

这道题让在旋转数组中搜索一个给定值,若存在返回坐标,若不存在返回 -1。我们还是考虑二分搜索法,但是这道题的难点在于不知道原数组在哪旋转了,还是用题目中给的例子来分析,对于数组 [0 1 2 4 5 6 7] 共有下列七种旋转方法(红色表示中点之前或者之后一定为有序的):

0  1  2   4  5  6  7

7  0  1   2  4  5  6

6  7  0   1  2  4  5

5  6  7   0  1  2  4

4  5  6  7   0  1  2

2  4  5  6   7  0  1

1  2  4  5   6  7  0

二分搜索法的关键在于获得了中间数后,判断下面要搜索左半段还是右半段,观察上面红色的数字都是升序的(Github 中可能无法显示颜色,参见博客园上的帖子),可以得出出规律,如果中间的数小于最右边的数,则右半段是有序的,若中间数大于最右边数,则左半段是有序的,我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,这样就可以确定保留哪半边了,代码如下:

解法一:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            if (nums[mid] < nums[right]) {
                if (nums[mid] < target && nums[right] >= target) left = mid + 1;
                else right = mid - 1;
            } else {
                if (nums[left] <= target && nums[mid] > target) right = mid - 1;
                else left = mid + 1;
            }
        }
        return -1;
    }
};

看了上面的解法,你可能会产生个疑问,为啥非得用中间的数字跟最右边的比较呢?难道跟最左边的数字比较不行吗,当中间的数字大于最左边的数字时,左半段也是有序的啊,如下所示(蓝色表示中点之前一定为有序的):

0  1  2   4   5  6  7

7  0  1   2  4  5  6

6  7  0   1  2  4  5

5  6  7   0  1  2  4

4  5  6  7   0  1  2

2  4  5  6   7  0  1

1  2  4  5   6  7  0

貌似也可以做,但是有一个问题,那就是在二分搜索中,nums[mid] 和 nums[left] 还有可能相等的,当数组中只有两个数字的时候,比如 [3, 1],那该去取那一边呢?由于只有两个数字且 nums[mid] 不等于 target,target 只有可能在右半边出现。最好的方法就是让其无法进入左半段,就需要左半段是有序的,而且由于一定无法同时满足 nums[left] <= target && nums[mid] > target,因为 nums[left] 和 nums[mid] 相等,同一个数怎么可能同时大于等于 target,又小于 target。由于这个条件不满足,则直接进入右半段继续搜索即可,所以等于的情况要加到 nums[mid] > nums[left] 的情况中,变成大于等于,参见代码如下:

解法二:

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0, right = nums.size() - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            if (nums[mid] >= nums[left]) {
                if (nums[left] <= target && nums[mid] > target) right = mid - 1;
                else left = mid + 1;
            } else {
                if (nums[mid] < target && nums[right] >= target) left = mid + 1;
                else right = mid - 1;
            }
        }
        return -1;
    }
};

讨论:这道题的二分搜索的解法实际上是博主之前的总结帖 LeetCode Binary Search Summary 二分搜索法小结 中的第五类,也是必须要将 right 初始化为 nums.size()-1,且循环条件必须为小于等于。二分搜索法真是博大精深,变化莫测啊~

思路2

这是一个我在网上看到的前端头条技术终面的一个算法题。

题目要求时间复杂度为logn,因此基本就是二分法了。 这道题目不是直接的有序数组,不然就是easy了。

首先要知道,我们随便选择一个点,将数组分为前后两部分,其中一部分一定是有序的。

具体步骤:

  • 我们可以先找出mid,然后根据mid来判断,mid是在有序的部分还是无序的部分

假如mid小于start,则mid一定在右边有序部分。
假如mid大于等于start, 则mid一定在左边有序部分。

注意等号的考虑

  • 然后我们继续判断target在哪一部分, 我们就可以舍弃另一部分了

我们只需要比较target和有序部分的边界关系就行了。 比如mid在右侧有序部分,即[mid, end]
那么我们只需要判断 target >= mid && target <= end 就能知道target在右侧有序部分,我们就
可以舍弃左边部分了(start = mid + 1), 反之亦然。

我们以([6,7,8,1,2,3,4,5], 4)为例讲解一下:

mark

mark

关键点解析

  • 二分法
  • 找出有序区间,然后根据target是否在有序区间舍弃一半元素

代码

  • 语言支持: Javascript,Python3
/*
 * @lc app=leetcode id=33 lang=javascript
 *
 * [33] Search in Rotated Sorted Array
 */
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
  // 时间复杂度:O(logn)
  // 空间复杂度:O(1)
  // [6,7,8,1,2,3,4,5]
  let start = 0;
  let end = nums.length - 1;

  while (start <= end) {
    const mid = start + ((end - start) >> 1);
    if (nums[mid] === target) return mid;

    // [start, mid]有序

    // ️⚠️注意这里的等号
    if (nums[mid] >= nums[start]) {
      //target 在 [start, mid] 之间

      // 其实target不可能等于nums[mid], 但是为了对称,我还是加上了等号
      if (target >= nums[start] && target <= nums[mid]) {
        end = mid - 1;
      } else {
        //target 不在 [start, mid] 之间
        start = mid + 1;
      }
    } else {
      // [mid, end]有序

      // target 在 [mid, end] 之间
      if (target >= nums[mid] && target <= nums[end]) {
        start = mid + 1;
      } else {
        // target 不在 [mid, end] 之间
        end = mid - 1;
      }
    }
  }

  return -1;
};

Python3 Code:

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        """用二分法,先判断左右两边哪一边是有序的,再判断是否在有序的列表之内"""
        if len(nums) <= 0:
            return -1

        left = 0
        right = len(nums) - 1
        while left < right:
            mid = (right - left) // 2 + left
            if nums[mid] == target:
                return mid
            
            # 如果中间的值大于最左边的值,说明左边有序
            if nums[mid] > nums[left]:
                if nums[left] <= target <= nums[mid]:
                    right = mid
                else:
                    # 这里 +1,因为上面是 <= 符号
                    left = mid + 1
            # 否则右边有序
            else:
                # 注意:这里必须是 mid+1,因为根据我们的比较方式,mid属于左边的序列
                if nums[mid+1] <= target <= nums[right]:
                    left = mid + 1
                else:
                    right = mid
                    
        return left if nums[left] == target else -1

本文参考自:
https://github.com/grandyang/leetcode/ &
https://github.com/azl397985856/leetcode


标题: daily leetcode - search-in-rotated-sorted-array - !
文章作者: lonuslan
文章链接: https://louislan.com/articles/2020/02/03/1580711436857.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Hi I'm LouisLan
    评论
    0 评论
avatar

取消