博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在排序数组中查找元素的第一个和最后一个位置
阅读量:3951 次
发布时间:2019-05-24

本文共 5307 字,大约阅读时间需要 17 分钟。

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

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

进阶:

你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8输出:[3,4]

示例 2:

输入:nums = [5,7,7,8,8,10], target = 6输出:[-1,-1]

思路与算法 : 二分查找

直观的思路肯定是从前往后遍历一遍。用两个变量记录第一次和最后一次遇见 target \textit{target} target 的下标,但这个方法的时间复杂度为 O ( n ) O(n) O(n) ,没有利用到数组升序排列的条件。

由于数组已经排序,因此整个数组是单调递增的,我们可以利用二分法来加速查找的过程。

  • 第 1 部分:查找 target 出现的第 1 个位置,这部分代码上面已经讲过了
  • 第 2 部分:查找 target 出现的最后 1 个位置,这部分代码上面已经讲过了。
  • 补充完整的代码
class Solution(object):    def searchRange(self, nums, target):        """        :type nums: List[int]        :type target: int        :rtype: List[int]        """        if len(nums) ==0:            return [-1,-1]        leftIdx = self.findFirstPosition(nums, target)        if leftIdx== -1:            return [-1,-1]        rightIdx = self.findLastPosition(nums, target)        return [leftIdx,rightIdx]    # 寻找最后一个位置    def findLastPosition(self,nums, target):        # 左右都闭合的区间 [l, r]        l, r = 0, len(nums) - 1        while l <= r:            mid = int(l + (r - l) / 2) # 防止计算时溢出            if nums[mid] == target:                # 只有这里不一样:不可以直接返回,应该继续向右边找,即 [mid + 1, right] 区间里找                # 收缩左边界                l = mid + 1;            # 应该继续向右边找,搜索区间变为 [mid+1, right]            elif nums[mid] < target:                 l = mid + 1            # 应该继续向左边找,搜索区间变为 [left, mid - 1]            elif nums[mid] > target:                 r = mid - 1        if r < 0 or nums[r] != target: return -1        return r    def findFirstPosition(self,nums, target):        l, r = 0, len(nums) - 1        while l <= r:            mid = int(l + (r - l) / 2) # 防止计算时溢出            if nums[mid] == target:                # ① 不可以直接返回,应该继续向左边找,即 [left,mid - 1] 区间里找                # 收缩右边界                r = mid - 1;            elif nums[mid] < target:  # 应该继续向右边找,搜索区间变为 [mid+1, right]                l = mid + 1            else: #  nums[mid] > target ,应该继续向左边找 ,搜索区间变为 [left, mid - 1]                r = mid - 1        # 此时 left 和 right 的位置关系是 [right, left],注意上面的 ①,此时 left 才是第 1 次元素出现的位置        # 因此还需要特别做一次判断        if l >= len(nums) or nums[l] != target:             return -1        return la = Solution()print(a.searchRange( nums = [5,7,7,8,8,10], target = 8))
[3, 4]

考虑 target \textit{target} target 开始和结束位置,其实我们要找的就是数组中「第一个等于 target \textit{target} target 的位置」(记为 leftIdx \textit{leftIdx} leftIdx )和「第一个大于 target \textit{target} target 的位置减一」(记为 rightIdx \textit{rightIdx} rightIdx)。

定义一个二分查找,寻找 leftIdx \textit{leftIdx} leftIdx 即为在数组中寻找第一个等于 target \textit{target} target 的下标,寻找 rightIdx \textit{rightIdx} rightIdx 即为在数组中寻找第一个大于 target \textit{target} target 的下标,然后将下标减一,即为最后一个位置。

最后,因为 target \textit{target} target 可能不存在数组中,因此我们需要重新校验我们得到的两个下标 leftIdx \textit{leftIdx} leftIdx rightIdx \textit{rightIdx} rightIdx,看是否符合条件,如果符合条件就返回 [ leftIdx , rightIdx ] [\textit{leftIdx},\textit{rightIdx}] [leftIdx,rightIdx] ,不符合就返回 [ − 1 , − 1 ] [-1,-1] [1,1]

class Solution(object):    def searchRange(self, nums, target):        """        :type nums: List[int]        :type target: int        :rtype: List[int]        """        def binarySearch(nums, target):            left = 0            right = len(nums) -1            while(left<=right):                mid = left +  (right-left) //2                if nums[mid]>=target:  #应该继续向左边找 ,搜索区间变为 [left, mid - 1]                    right = mid-1      #寻找第一个满足条件的点, 即使得right在满足target的数的最左边                else:                                     left = mid+1       # 应该继续向右边找,搜索区间变为 [mid+1, right]            return left                    leftIdx = binarySearch(nums, target)        rightIdx = binarySearch(nums, target+1) -1        if leftIdx == len(nums) or nums[leftIdx] !=target:            return [-1,-1]        else:            return [leftIdx,rightIdx]a = Solution()a.searchRange(nums = [1,3], target = 1)
[0, 0]

为了代码的复用,我们定义 binarySearch(nums, target, lower) 表示在 nums \textit{nums} nums 数组中二分查找 target \textit{target} target 的位置,如果 lower \textit{lower} lower t r u e \rm true true ,则查找第一个等于 target \textit{target} target 的下标,即寻找左侧边界。否则查找第一个大于 target \textit{target} target 的下标,-1为寻找右侧边界。

最后,因为 target \textit{target} target 可能不存在数组中,因此我们需要重新校验我们得到的两个下标 leftIdx \textit{leftIdx} leftIdx rightIdx \textit{rightIdx} rightIdx,看是否符合条件,如果符合条件就返回 [ leftIdx , rightIdx ] [\textit{leftIdx},\textit{rightIdx}] [leftIdx,rightIdx] ,不符合就返回 [ − 1 , − 1 ] [-1,-1] [1,1].

class Solution(object):    def searchRange(self, nums, target):        """        :type nums: List[int]        :type target: int        :rtype: List[int]        """        def binarySearch(nums, target,lower):            left = 0            right = len(nums) -1            ans=len(nums)            while(left<=right):                mid = left +  (right-left) //2                if (nums[mid]>target or (lower and nums[mid] >=target)):                    right = mid-1                    ans = mid                else:                    left = mid+1            return ans                    leftIdx = binarySearch(nums, target,True)        rightIdx = binarySearch(nums, target,False) -1        if (leftIdx <= rightIdx and rightIdx < len(nums) and nums[leftIdx] == target and nums[rightIdx] == target):            return [leftIdx,rightIdx]        return [-1,-1]

计算过程:

在这里插入图片描述

转载地址:http://xdyzi.baihongyu.com/

你可能感兴趣的文章
1017 A除以B (20 分)
查看>>
1019 数字黑洞 (20 分)
查看>>
1032 挖掘机技术哪家强 (20 分)
查看>>
今夕何夕 HDU - 6112 ( 模拟 )
查看>>
Dividing HDU - 1059 ( 多重背包 - 二进制简化 )
查看>>
Robberies HDU - 2955 ( 0-1背包 )
查看>>
FATE HDU - 2459 ( 二维完全背包 )
查看>>
B. Working out CodeForces - 429B (动态规划)
查看>>
10635 - Prince and Princess UVA-10635 (最长公共子序列的O(nlogn)的解法:LCS转换为LIS)
查看>>
Sizeof和Strlen
查看>>
lower_bound和upper_bound
查看>>
Subsequence POJ - 3061 ( 尺取法 )
查看>>
常见HTTP状态码大全
查看>>
这16个数据可视化案例,惊艳了全球数据行业
查看>>
大数据死亡率报告揭秘:SUV与轿车到底谁更危险?
查看>>
2017年网络流行语TOP20 , 没用过算我输!
查看>>
看完这13张图,不得不佩服还是外国人会玩人工智能
查看>>
从零开始用Python构造决策树(附公式、代码)
查看>>
精华 | 12个关键词告诉你告诉你什么是机器学习(基础篇)
查看>>
15个优秀的开源项目,让你轻松应对Android开发
查看>>