代码随想录算法训练营第一天| 704. 二分查找、27. 移除元素。

276 阅读3分钟

704. 二分查找

1. 文章链接

文档讲解:代码随想录 (programmercarl.com)

2. 看到题目的第一想法

有序数组->二分法

左边界left, 右边界right, 找到中间位置的元素的idx

如果nums[idx]<target,说明targetnums[idx + 1, right]的范围内。

如果nums[idx]>target,说明targetnums[left, idx - 1]的范围内。

如果nums[idx]==target,说明target已经找到,返回idx

但是什么情况下target算是没找到,返回-1呢?

答:应该是左右边界leftright已经相等了,这种情况下可能边界上也存在我们要的target, 因为明确判断不是targetidx已经被我们通过+1,-1的方式排除在范围之外了。

所以只有当leftright位置互换的情况下,即left > right的情况下才能下定论就是找不到了。

而且二分查找并不是每个区域都要探索,而是一半一半地缩小范围,所以实际上是O(logn)O(logn)的时间复杂度。

所以也不会太耗时。

class Solution:
    def binary_search(self, nums: List[int], left_int: int, right_int: int, target: int) -> int:
        idx_int = (left_int + right_int) >> 1
        if nums[idx_int] < target:
            # target在右侧。
            if idx_int == right_int:
                # 说明没找到
                return -1
            loc_int = self.binary_search(
                nums=nums,
                left_int=idx_int + 1,
                right_int=right_int,
                target=target
            )
        elif nums[idx_int] > target:
            # target在左侧。
            if left_int == idx_int:
                return -1
            loc_int = self.binary_search(
                nums=nums,
                left_int=left_int,
                right_int=idx_int - 1,
                target=target
            )
        else:
            loc_int = idx_int
        return loc_int


    def search(self, nums: List[int], target: int) -> int:
        left_int = 0
        right_int = len(nums) - 1  # 右边界也包含在搜索范围内。
        return self.binary_search(nums, left_int, right_int, target)

3. 看完代码随想录之后的想法

二分法不仅仅需要数组是有序数组,而且还要求数组中没有重复元素

我保持了左闭右闭的循环不变量,但我没有用循环,而是用了递归。

下面这种找中位点的做法更好,可以防止溢出:

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

于是我按照循环又写了一遍:

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left_int = 0
        right_int = len(nums) - 1  # 右边界也包含在搜索范围内。
        while left_int <= right_int:
            idx_int = left_int + (right_int - left_int) // 2
            if nums[idx_int] < target:
                # 在右侧
                left_int = idx_int + 1
            elif nums[idx_int] > target:
                # 在左侧
                right_int = idx_int - 1
            else:
                return idx_int
        return -1

4. 实现过程中遇到哪些困难

如果用递归,不能使用直接截取num的一段传给下一层递归的方法,因为我们需要记录真实的坐标,所以只能传完整的nums.

5. 学习时长

构思时长:50分钟,因为中间有各种事情打扰。实际应该有30分钟吧。

实现+修改:30分钟。

27. 移除元素

1. 文章链接

文档讲解:代码随想录 (programmercarl.com)

2. 看到题目的第一想法

nums数组长度不超过100,我觉得暴力求解也可以。

最坏的情况就是0n10\sim n-1每次都移动,每次移动n-i个元素。

那也大概是n2n^2的时间。

看了力扣上的提示,我发现可以每次从最后一个位置拿一个元素来覆盖指定移除的元素。

用于覆盖的元素原来的位置的tail--,这样tail每次都指向最后一个有效元素。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        tail = len(nums) - 1
        val_idx = 0
        while val_idx <= tail:
            if nums[val_idx] == val:
                nums[val_idx] = nums[tail]
                tail -= 1
            else:
                val_idx += 1
        return tail + 1

3. 看完代码随想录之后的想法

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

其实我的实现方法也是一种双指针的思想,但是感觉不按套路出牌。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        slow_idx = 0
        for fast_idx in range(len(nums)):
            if nums[fast_idx] != val:
                nums[slow_idx] = nums[fast_idx]
                slow_idx += 1
            # 如果fast_idx指向了val,不应该更新到nums[slow_idx]上,所以直接跳过看下一个。
        return slow_idx

4. 实现过程中遇到哪些困难

当我打算用尾部元素替换前面的nums[idx]==val的元素时,我发现存在一个疏忽,那就是尾部元素有可能也是nums[tail]==val的,这样就白替换了,我想到的补救方法是,被覆盖的位置的idx不要动,等待下一次判断,如果还判断nums[idx]==val时,则还能那下一个新的tail去替换。

5. 学习时长

构思+实现:<30分钟