【Day1】代码随想录算法训练营 - 数组(一)

题目一:704. 二分查找

1. 题目描述

在这里插入图片描述

2. 解题思路

看到题目第一反应index函数,原谅早已把数据结构算法抛之脑后也没系统学过

  • 该题目核心确定leftright边界指示变量,while循环即可
  • 前置条件:数组有序+无重复元素(有重复元素也可返回下标,不一定是该元素第一次在数组中出现的下标)
  • 左闭右闭其实和左闭右开核心都一样,只不过左闭右开中右边界为虚拟边界

3. 代码

3.1 左闭右闭
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        list_len = len(nums)
        left = 0
        right = list_len - 1
        while left <= right:
            middle = (left + right) // 2
            if target > nums[middle]:
                left = middle + 1
            elif target < nums[middle]:
                right = middle - 1
            else:
                return middle
        return -1
3.2 左闭右开
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums)
        while left < right:
            middle = (left + right) // 2
            if target > nums[middle]:
                left = middle + 1
            elif target < nums[middle]:
                right = middle
            else:
                return middle
        return -1

题目二:35. 搜索插入位置

1. 题目描述

在这里插入图片描述

2. 题目分析

该题目类似于# 704,但是返回条件略微复杂

  • 前提:数组无重复元素
  • 复杂之处在于要针对while循环退出时的情况进行精确分析(这里不分析找到元素的情况,因为该情况和# 704相同)

2.1 左闭右开

2.1.1 解题思路
  • 左闭右开的优点在于while循环的退出条件是固定的,即left==right

首先确定leftright如何变化,leftmiddle+1得到,right则直接等于middle, 首先证明left移动时永远不会大于right, 即middle+1<=right恒成立
证明: ∵ m i d d l e = ⌊ ( l e f t + r i g h t ) / 2 ⌋ ∴ m i d d l e + 1 = ⌊ ( l e f t + r i g h t ) / 2 ⌋ + 1 ∵ l e f t 移动之前 l e f t < r i g h t 恒成立 ∴ l e f t ≤ r i g h t − 1 恒成立 ∴ ( m i d d l e + 1 ) ∗ 2 = ⌊ ( l e f t + r i g h t ) / 2 ⌋ ∗ 2 + 2 ≤ ⌊ ( r i g h t − 1 + r i g h t ) / 2 ⌋ ∗ 2 + 2 ≤ ( r i g h t − 1 ) ∗ 2 + 2 ≤ 2 ∗ r i g h t ∴ m i d d l e + 1 ≤ r i g h t \begin{aligned} &\because middle = \lfloor(left + right) / 2\rfloor \\ &\therefore middle + 1 = \lfloor(left + right) / 2\rfloor+1\\ &\because left移动之前left\lt{right}恒成立\\ &\therefore{left}\le{right-1}恒成立\\ &\begin{aligned} \therefore (middle + 1)*2 &= \lfloor(left+right)/2\rfloor*2+2 \\ &\le{\lfloor(right-1+right)/2\rfloor*2+2} \\ &\le{(right-1)*2+2} \\ &\le{2*right} \end{aligned}\\ &\therefore middle+1\le{right} \end{aligned} middle=⌊(left+right)/2middle+1=⌊(left+right)/2+1left移动之前left<right恒成立leftright1恒成立(middle+1)2=⌊(left+right)/22+2⌊(right1+right)/22+2(right1)2+22rightmiddle+1right
关键在于middle由向下取整获得,其实默想一下向下取整再加1永远不会超出右边界right
还需证right移动时永远不会小于left, 即left<=middle恒成立,显然成立
因此退出while循环时,left==right

  • 退出时left==right,这里取left进一步分析,此时存在两种情况
    • 0<=left<len(nums),此时小于目标值target<nums[left]返回left即可,大于则返回left+1
    • left==len(nums),该情况也是容易遗漏的一点,left不断向right靠拢最后等于right,如果right一直等于数组长度(虚拟边界),此时nums[left]不存在,直接返回left即可
2.1.2 代码
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        list_len = len(nums)
        left = 0
        right = list_len
        while left < right:
            middle = (left + right) // 2
            if nums[middle] < target:
                left = middle + 1
            elif nums[middle] == target:
                return middle
            else:
                right = middle
        if left == len(nums) or target < nums[left]:
            return left
        else:
            return left + 1

2.2 左闭右闭

2.2.1 解题思路
  • 左闭右闭的优点在于left不会超出数组边界
  • 与左闭右开不同的区别在于 right=middle-1会使得存在right<left的情况,导致while循环退出时不一定left==right
    注意到这些不同,针对代码局部修改即可
2.2.2 代码
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        list_len = len(nums)
        left = 0
        right = list_len - 1
        while left < right:
            middle = (left + right) // 2
            if nums[middle] < target:
                left = middle + 1
            elif nums[middle] == target:
                return middle
            else:
                right = middle - 1
        if target < nums[left] or left == right and nums[left] == target:
                return left
        else:
            return left + 1

题目三:27. 移除元素

1. 题目描述

简单描述为删除指定列表中值等于目标值的所有元素,返回新列表的长度,新列表元素顺序可打乱。
在这里插入图片描述

2. 题目分析

2.1 暴力解法

2.1.1 解题思路

该题目需要先对列表元素逐个进行排查,如果该元素等于目标值val,则从该元素往后找到非val的值与该元素进行替换。即:

  • 找到val值时,for循环或者while循环向后依次遍历直到找到非val
  • 替换val值和非val

注:如果第二层for循环或者while循环一直遍历到最后一个元素才找到非val值或一直遍历到最后一个元素都没找到val值,则说明整个列表已经遍历完毕(即没有元素再需要被替换),此时可以直接结束程序

2.1.2 代码
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        total_len = 0
        for i in range(len(nums)):
            if nums[i] != val:
                total_len += 1
                continue
            else:
                j = i + 1
                while j < len(nums):
                    if nums[j] != val:
                        nums[i], nums[j] = nums[j], nums[i]
                        total_len += 1
                        break
                    j += 1
                if j >= len(nums) - 1:
                    return total_len
        return total_len

2.2 双指针法

2.2.1 解题思路

双指针法指的是用一个后指针从列表尾部向前检索,直到找到非val值,以此替换暴力解法中的第二层for循环或者while循环,时间复杂度为O(n)

2.2.2 代码
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        left = 0
        right = len(nums) - 1
        total_len = 0
        while left <= right:
            if nums[left] != val:
                total_len += 1
                left += 1
            else:
                if nums[right] != val:
                    nums[left], nums[right] = nums[right], nums[left]
                    total_len += 1
                    left += 1
                right -= 1
        return total_len
2.2.3 扩展 (代码训练营官方代码)

该方法使用右指针前后探索,左指针每次只移动一步。
优点

  • 时间复杂度同样为O(n)
  • 不改变相对元素位置。
  • 只用右指针作为循环条件会保证找到的每一个非val值都会被移到最前面,所以左指针可以随意覆盖(不用判断是否等于val
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        # 快指针遍历元素
        fast = 0
        # 慢指针记录位置
        slow = 0
        for fast in range(len(nums)):
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
        return slow

2.3 总结

  • 双指针优点在于代码简便、时间复杂度低,缺点在于自定义的双指针代码会打乱原列表元素相对位置
  • 暴力解法(加上注)优点在于不会打乱原列表元素的顺序,缺点在于时间复杂度略高

两种解法都需要考虑到特殊情况以及边界条件,特殊情况为

  • [1], 1 (只有一个元素,即被删除元素)
  • [1], 2 (只有一个元素,该元素不等于被删除元素)
  • [], 1 (空列表)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值