34在排序数组中查找元素的第一个和最后一个位置

题目:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。如果数组中不存在目标值,返回 [-1, -1]。
链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array

法一:自己的代码

思路:虽然right=len(nums)-1,即左闭右闭区间,但没有用left<right终止循环,当left=mid时,此时就做出判断终止循环,即可以灵活应用,不是必须当left==right的时候终止循环,

from typing import List
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        right = len(nums) - 1
        if right == -1:
            return [-1, -1]
        elif right == 0:
            if nums[0] == target:
                return [0,0]
            else:
                return [-1, -1]
        # 先查找左端点
        def find_left(left, right):
            while left < right:
                mid = (left+right) >> 1
                # 等于的时候,目标值如果存在一定在等于的这两个值里
                if left == mid:
                    if nums[right] == target:
                        if nums[left] == target:
                            return left
                        else:
                            return right
                    elif nums[left] == target:
                        return left
                    else:
                        return -1
                if nums[mid] >= target:
                    right = mid
                else:
                    left = mid + 1
            return left if nums[left] == target else -1
        # return find_left(0, right)
        # 再查找右端点
        def find_right(left, right):
            while left < right:
                mid = (left+right) >> 1
                if left == mid:
                    if nums[left] == target:
                        if nums[right] == target:
                            return right
                        else:
                            return left
                    elif nums[right] == target:
                        return right
                    else:
                        return -1
                # 只有大于才让右端点左移,
                if nums[mid] > target:
                    right = mid
                else:
                    left = mid
        k = find_left(0, right)
        if k == -1:
            return [-1, -1]
        else:
            return [k, find_right(0, right)]
View Code

改进后的代码,right=len(nums),左闭右开区间

from typing import List
class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        right = len(nums)
        l = right
        # 找左侧边界的二分查找
        def find_left(left, right):
            # 搜索区间最短的时候有一个元素,为左边的left
            while left < right:
                mid = (left + right) >> 1
                if nums[mid] == target:
                    right = mid
                # 右边是开区间,而mid一定不满足条件,所以right直接取mid
                elif nums[mid] > target:
                    right = mid
                # 左边是闭区间,mid一定不满足条件,直接将其排除,left取mid+1
                # 综上可见,left和right本质上都一样,都是对已经作过判断的位置mid
                # 不再进行判断,但是由于left闭right开,导致了排序mid位置的方法不一样
                elif nums[mid] < target:
                    left = mid + 1
            if left == l:
                return -1
            return left if nums[left] == target else -1
        # 找右侧边界的二分查找
        def find_right(left, right):
            while left < right:
                mid = (left + right) >> 1
                # 注意这里要加1,因为left是闭区间
                # 如果不加1会出现死循环,如left等于2,right等于3时,这时mid每次都是left,陷入死循环
                if nums[mid] == target:
                    left = mid + 1
                # 如果大于目标值,则该值一定不满足条件,
                elif nums[mid] > target:
                    right = mid
                # 如果小于,右移一个,因为左边是闭区间,
                elif nums[mid] < target:
                    left = mid + 1
            # 注意当left == right的时候,循环结束,但由于nums[mid] > target时,right=mid,即nums[right]一定不等于target
            # 也就是说nums[left-1]才可能等于target,故需要判断nums[left-1]
            if nums[max(left-1, 0)] == target:
                return max(left-1, 0)
            else:
                return -1
        k = find_left(0, right)
        if k == -1:
            return [-1,-1]
        else:
            return [k, find_right(0, right)]
if __name__ == '__main__':
    solution = Solution()
    # result = solution.searchRange(nums = [5,7,7,8,8,8,8,9,10], target = 8)
    # result = solution.searchRange(nums = [7,7,7,7], target = 7)
    result = solution.searchRange(nums = [-4, -2, -1], target = -5)
    # result = solution.searchRange(nums = [5,7,7,8,8,10], target = 8)
    # result = solution.searchRange(nums = [1,2,3,4,5,8,8,8,8,8,8,8], target = 8)
    # result = solution.searchRange(nums = [1,1,1,1,1,1,1,1,1,2,3], target = -22)
    # result = solution.searchRange(nums = [0], target = 0)
    # result = solution.searchRange(nums = [8,8,8,8,8,8,8], target = 8)
    print(result)
View Code

注意上面求右侧边界时,是让左端点向中间靠拢,即左端点小于等于目标值是向右靠,事实上也可以让右端点在大于目标值时让右端点左移一位即mid-1,左端点不变等于mid,如下

def rightloc(self, nums,target):
    lo, hi = 0, len(nums) - 1
    while lo < hi:
        mid = (lo + hi + 1) >> 1
        if nums[mid] <= target:
            lo = mid
        else:
            hi = mid - 1
    return lo if not lo == len(nums) and nums[lo] == target else -1
View Code

总结:对于求右侧边界,事实上移动左右端点都行,只要把目标值锁定在搜索区间肯定是对的,只不过后处理的方法可能不同,

ttt

 

posted on 2020-02-23 20:35  吃我一枪  阅读(192)  评论(0编辑  收藏  举报

导航