代码随想录|Day01|数组01|704. 二分查找、27. 移除元素、35. 搜索插入位置、34. 在排序数组中查找元素的第一个和最后一个位置

本文介绍了二分查找算法在有序数组中的高效应用,包括查找元素、移除元素时的优化方法,以及在含有重复元素的数组中寻找第一个和最后一个目标值的位置。通过快慢指针和边界判断,提高了搜索和插入操作的时间复杂度至O(logn)。
摘要由CSDN通过智能技术生成

704. 二分查找

暴力:单个for循环遍历,时间复杂度为 O(n)

思路:由于本题是一个有序数组,并且无重复元素,因此可以考虑使用效率更高的二分法,使得每次的搜索规模减半。首先将整个数组设想为一个左闭右闭的区间 [left, right],然后将区间中点的元素 mid 与目标元素 target 对比。在本题非递减数组的情况下,如果 mid < target 则说明我们的 target 在 mid 右边,此时只需将左边界 left 更新为 mid + 1

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # 使用二分法的前提:1.有序数组 2.无重复元素
        left, right = 0, len(nums)-1
        # 这里采用左闭右闭区间
        # 因此左边界=右边界(left == right)是合法的
        while left <= right:
            # 避免溢出,等效于 (left + right) // 2
            mid = left + (right - left) // 2
            
            if nums[mid] < target:
                left = mid + 1
            elif nums[mid] > target:
                right = mid - 1
            else:
                return mid
        return -1
# 时间复杂度:
# O(log n)

27. 移除元素

暴力:遍历数组,若遇到需要移除的元素,将此元素后面所有的元素向前挪动一位。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:

        # 暴力
        length = len(nums)
        i = 0

        while i < length:

            if nums[i] == val:

                for j in range(i + 1, length):
                    nums[j-1] = nums[j]
                
                length -= 1
            
            else:
                i += 1

        return length
# 时间复杂度:
# O(n²)

思路:使用快慢指针,快指针寻找新数组元素,慢指针更新新数组元素,只需遍历一次。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:

        fast, slow = 0, 0

        while fast < len(nums):
            # 若快指针找到了新数组元素,则通过慢指针更新,然后两指针继续后移
            if nums[fast] != val:
                nums[slow] = nums[fast]
                fast += 1
                slow += 1
            # 若快指针指向的元素不属于新数组,则慢指针保持不动,直到快指针找到新数组元素
            else:
                fast += 1
        return slow
# 时间复杂度:
# O(n)

 拓展1:35. 搜索插入位置

本题与704的不同:若目标不存在于数组,不说返回 -1,而是返回元素按顺序插入的位置。因此我们只需要判断,当二分搜索结束后,左右边界分别在什么位置。

在左闭右闭且为升序数组的情况下,有几个关键的观察点:

1. 每次 nums[mid] < target,都需要将 left 向右移动,直到到达第一个不小于 target 的位置。

2. 每次 nums[mid] > target,都需要将 right 向左移动,直到到达第一个不大于 target 的位置。

因此,若数组不存在 target,则应该安插到 left 处或 right + 1 处。

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        # nums为无重复元素的升序数组,符合使用二分法的前提
        left, right = 0, len(nums) - 1

        while left <= right:

            mid = left + (right - left) // 2

            if nums[mid] < target:
                left = mid + 1
            elif nums[mid] > target:
                right = mid - 1
            else:
                return mid
        # 与704返回-1不同,这次需要返回按顺序插入的位置
        return left
# 时间复杂度:
# O(log n)

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

和704的区别是,本题可能包含重复元素,但是依然可以通过使用两次二分法来分别找出第一个和最后一个位置,只需要在标准二分法的基础上多写一个边界判断。

以寻找第一个位置举例,若二分法找到了一个 mid == target,此时我们需要额外判断 mid 是否为 target 出现的第一个位置。如果 mid 为数组第一个元素,或者 mid 的前一个元素(mid - 1)依然等于 target,则说明此 mid 并不是第一个位置,此时我们需要将右边界左移,再次进行二分搜索。

简而言之,本题搜索的目标不仅仅是 value == target,还需要满足“是第一个出现的”位置条件。

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:

        def findFirst(nums, target):
            left, right = 0, len(nums)-1
            while left <= right:
                mid = left + (right - left) // 2
                if nums[mid] < target:
                    left = mid + 1
                elif nums[mid] > target:
                    right = mid - 1
                # 由于元素可能不唯一,此时需要向左寻找target出现的第一个位置    
                else:
                    # 判断当前找到的mid是否为target出现的第一个位置
                    # 要么mid是数组首位,要么mid前一个元素不等于target
                    # 如果mid不是target首次出现的位置,我们需要缩小右边界继续寻找
                    if mid == 0 or nums[mid-1] != target:
                        return mid
                    else:
                        right = mid - 1
            return -1
        
        def findRight(nums, target):
            left, right = 0, len(nums)-1
            while left <= right:
                mid = left + (right - left) // 2
                if nums[mid] < target:
                    left = mid + 1
                elif nums[mid] > target:
                    right = mid - 1
                else:
                    if mid == len(nums) - 1 or nums[mid+1] != target:
                        return mid
                    else:
                        left = mid + 1
            return -1
        
        return [findFirst(nums, target), findRight(nums, target)]
# 时间复杂度:
# O(log n) + O(log n) = O(log n)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值