leetcode 81 Search in Rotated Sorted Array II 二分循环变种

本文深入探讨了在已排序数组被旋转的情况下,如何使用二分查找算法来定位目标值。通过两种解法,即两次二分查找和一次二分查找,文章详细解释了在不同情况下如何正确地定位最大最小值位置,以及如何处理重复元素和数组边界条件,最终高效地实现了搜索目标值的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

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

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

You are given a target value to search. If found in the array return true, otherwise return false.

Example 1:

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

Example 2:

Input: nums = [2,5,6,0,0,1,2], target = 3
Output: false

Follow up:

  • This is a follow up problem to Search in Rotated Sorted Array, where nums may contain duplicates.
  • Would this affect the run-time complexity? How and why?

--------------------------------------------------------------------------------------------------

这题在target和mid比较完之后,就陷入了和left和right大小的讨论,稍有不慎就会出错。因此,有两种解法:

解法一:两次二分

解法一:两次二分:第一次二分找到上图中max min的位置(leetcode 153 154 Find Minimum in Rotated Sorted Array I II 二分砍掉一边跨步移动,不找分界线_taoqick的博客-优快云博客)。但是注意如果找到这个位置是0,还需要从数组末尾往前再看一段,防止dup的坑。

class Solution:
    def search(self, nums, target) -> int:
        def find_target(arr, lo, hi, tgt):
            l,r = lo,hi
            while (l <= r):
                mid = l + ((r-l)>>1)
                if (arr[mid] == tgt):
                    return True
                if (tgt < arr[mid]):
                    r = mid-1
                elif (arr[mid] < tgt):
                    l = mid+1
            return False #since arr[lo] < target, l at least lo_1

        def find_min_pos(arr):
            l, r = 0, len(arr) - 1
            while (l < r and arr[l] == arr[r]):
                l += 1
            while (l <= r):
                mid = l + ((r-l)>>1)
                if (arr[mid] < arr[r]):
                    r = mid
                elif (arr[mid] > arr[r]):
                    l = mid+1
                else:
                    r -= 1
            return l
        if (not nums):
            return False
        min_pos = find_min_pos(nums)
        #print("min_pos={0}".format(min_pos))
        if (nums[min_pos] > target):
            return False
        elif (nums[min_pos-1] < target):
            return False
        elif (min_pos == 0):
            return find_target(nums, 0, len(nums)-1, target)
        elif (target >= nums[0]):
            return find_target(nums, 0, min_pos-1, target)
        else:
            return find_target(nums, min_pos, len(nums)-1, target)
s = Solution()
print(s.search([1,1,3,1], 3))

解法二:一次二分

关键思路在于:当存在l<r且nums[l]<=nums[r]时,意味着下标是l到r之间的数都在[nums[l],nums[r]]这个范围,也就是说明这中间没有坑;当存在l<r且nums[l]>nums[r]时,意味着这中间有坑,也就是这中间不存在(nums[l],nums[r])这个范围的数。

除此之外,还有几个注意点:

1. 先假设所有数各不相等,当nums[left]<nums[mid]时,只有nums[left] <= target或者target<nums[mid]并不足以说明target就在这中间,一定要写全nums[left] <= target < nums[mid]

2. 为啥是nums[left] <= target < nums[mid]而不是nums[left] < target < nums[mid],这个根据前面的target == nums[mid]排除掉可能相等的情况即可。

3. 当存在相等情况下时,要补充的思路是nums[mid]分别和nums[l]、nums[r]相等应该把左右边界没用的划拉掉。这个划拉的思路有两种,但都需要注意划拉完后mid就不再是开始时候的mid了。思路一在划拉完后通过continue重新计算mid;思路二控制left和right的下标不能越过mid,但要把nums[left] == nums[mid]情况单独再讨论下。个人感觉思路二更绕,思路一更清晰点

一次二分思路一:

​
class Solution:
    def search(self, nums, target):
        left, right = 0, len(nums)-1
        while (left <= right):
            mid = left + (right-left) // 2
            if target == nums[mid]:
                return True
            if nums[left] == nums[mid]:
                left += 1
                continue
            if nums[right] == nums[mid]:
                right -= 1
                continue

            if (nums[left] < nums[mid]):
                if nums[left] <= target and target < nums[mid]:
                    right = mid - 1
                else:
                    left = mid + 1
            elif (nums[left] > nums[mid]):
                if nums[mid] < target and target <= nums[right]:
                    left = mid+1
                else:
                    right = mid-1
        return False

​

一次二分思路二:

class Solution:
    def search(self, nums: List[int], target: int) -> bool:
        l,r = 0,len(nums)-1
        while (l<=r):
            mid=l+((r-l)>>1)
            if nums[mid] == target:
                return True
            while (l<mid and nums[l]==nums[mid]):
                l += 1
            while (mid<r and nums[r]==nums[mid]):
                r -= 1
            
            # if (nums[mid] >= nums[r]):  #不行
            # if (nums[l] < nums[mid]):  #不行
            # if (nums[l] <= nums[mid]):  #行了,等的情况扔给判断和target相等的那边
            if (nums[mid] > nums[r]):  #也行,等的情况扔给判断和target相等的那边
                if (nums[l]<=target and target<nums[mid]):
                    # 左边没有坑,且目标就在左边
                    r = mid-1
                else:
                    l = mid+1
            else:
                if (nums[mid]<target and target<=nums[r]):
                    # 右边没有坑,且目标就在右边
                    l = mid+1
                else:
                    r = mid-1
        return False

            

变种一:在一个rotated 可以重复数组中找<=target的最大元素值

这题还有一个变种,在一个rotated 可以重复数组中找<=target的最大元素值。利用解法一可以迅速简化:

import random


class Solution:
    def lower(self, nums, target):
        lst = [num for num in nums if num <= target]
        return max(lst) if lst else None

    def search(self, nums, target) -> int:
        def find_target(arr, lo, hi, tgt):
            l, r = lo, hi
            while (l <= r):
                mid = l + ((r - l) >> 1)
                if (arr[mid] == tgt):
                    return tgt
                if (tgt < arr[mid]):
                    r = mid - 1
                elif (arr[mid] < tgt):
                    l = mid + 1
            return arr[l - 1]  # since arr[lo] < target, l at least lo+1

        def find_min_pos(arr):
            l, r = 0, len(arr) - 1
            while (l < r and arr[l] == arr[r]): #针对bug1的另外一种fix方式
                l += 1
            while (l <= r):
                mid = l + ((r - l) >> 1)
                if (arr[mid] < arr[r]):
                    r = mid
                elif (arr[mid] > arr[r]):
                    l = mid + 1
                else:
                    r -= 1
            # if (l == 0):  # bug1
            #     pos = len(arr)
            #     while (pos >= 1 and arr[pos - 1] == arr[0]):
            #         pos -= 1
            #     return 0 if (pos == len(arr)) else pos
            return l

        if (not nums):  # bug2
            return None
        min_pos = find_min_pos(nums)
        # print("min_pos={0}".format(min_pos))
        if (nums[min_pos] > target):
            return None
        elif (nums[min_pos - 1] < target):
            return nums[min_pos - 1]
        elif (min_pos == 0):
            return find_target(nums, 0, len(nums) - 1, target)
        elif (target >= nums[0]):
            return find_target(nums, 0, min_pos - 1, target)
        else:
            return find_target(nums, min_pos, len(nums) - 1, target)


s = Solution()
cols = 500

for _ in range(1000):
    mid, target = random.randint(0, cols), random.randint(-10, cols + 10)
    raw = sorted([random.randint(1, cols) for i in range(cols)])
    arr = raw[mid:] + raw[:mid]
    # print('target={0} arr={1}'.format(target, arr))
    r1 = s.lower(arr, target)
    r2 = s.search(arr, target)
    if (r1 != r2):
        print("Error r1:{0} r2:{1}".format(r1, r2))
        print('target={0} arr={1}'.format(target, arr))
print('Finished!!!')

变种二:有序数组截成三段,交换第一段和第三段,然后给一个数据进行二分查找

import random
from typing import List

class Solution:
    def brute_force(self, nums, target):
        return target in nums

    def search(self, nums: List[int], target: int) -> bool:
        l, r = 0, len(nums) - 1
        while (l <= r):
            mid = l + ((r - l) >> 1)
            if nums[mid] == target:
                return True
            while (l < mid and nums[l] == nums[mid]):
                l += 1
            while (mid < r and nums[r] == nums[mid]):
                r -= 1

            if (nums[l] <= nums[mid]):
                # 左边没有坑
                if (nums[l] <= target and target < nums[mid]):
                    r = mid - 1
                else:
                    l = mid + 1
            elif (nums[mid] <= nums[r]):
                # 右边没有坑
                if (nums[mid] < target and target <= nums[r]):
                    l = mid + 1
                else:
                    r = mid - 1
            elif (nums[l] > nums[mid] and nums[mid] > nums[r]):
                # 两边都有坑,l,mid,r三个点,切割成4部分,分别讨论
                if target <= nums[r]:
                    l = mid + 1
                elif target >= nums[l]:
                    r = mid - 1
                elif nums[mid] > target and target > nums[r]:
                    # 右边有坑,坑范围内的一定不可以
                    r = mid - 1
                elif nums[l] > target and target > nums[mid]:
                    # 左边有坑,坑范围内的一定不可以
                    l = mid + 1

        return False
s = Solution()
cols = 10

for _ in range(1000):
    mid1, target = random.randint(0, cols-1), random.randint(-10, cols + 10)
    mid2 = random.randint(mid1+1, cols)
    raw = sorted([random.randint(1, cols) for i in range(cols)])
    arr = raw[mid2:] + raw[mid1:mid2] + raw[:mid1]
    # print('target={0} arr={1}'.format(target, arr))
    r1 = s.brute_force(arr, target)
    r2 = s.search(arr, target)
    if (r1 != r2):
        print("Error r1:{0} r2:{1}".format(r1, r2))
        print('target={0} arr={1}'.format(target, arr))
print('Finished!!!')

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值