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

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

数组理论

数组array:存放在连续内存空间上的相同类型数据的集合。数据在数组中配有下标索引,我们可以通过定位下标索引的方式获取对应的数据。
在这里插入图片描述
通过此图我们可以看出:

  • 数组下标索引从0开始
  • “连续内存空间”体现在对应的内存地址是连续的

由于数组内存地址是来连续的,因此在对数组进行插入或者删除的操作时,会导致其相邻的数据对应的下标和内存地址也发生变化。
变化通过覆盖实现,即将原来的数值用新的数值进行替换或覆盖。删除中,新数值为相邻的后续数据,将其往前移动一位;插入中,将数据往后移动一位。


704. 二分查找

Leetcode 704.二分查找 题目链接

B站视频对应讲解 (超详细!!!)

思路

将目标值与数组的中间元素进行比较,从而确定目标值可能在的区间,然后将搜索范围缩小一半,重复这个过程,直到找到目标值或确定目标值不存在。

时间复杂度

二分查找的时间复杂度是O(log n)。每次查找都将搜索范围缩小一半,因此时间复杂度是对数级别的。在每次迭代中,查找范围都会减半,直到找到目标值或确定目标值不存在。

区间规则

实现二分查找的代码需要注意区间规则的设置。可视为左闭右闭 (left<=right) 和左闭右开 (left<right) 两种情况。

代码

左闭右闭

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left,right=0,len(nums)-1
        # 由于left<=right,要确保right是可以取得到的数字
        # 因此right初始值要设为List的最后一位元素的索引值
        while(left<=right): # [left,right]左闭右闭
            middle=left+(right-left)//2 
            #等同于(left+right)/2,目的是防止内存溢出
            if nums[middle]<target:
                left=middle+1
                # left为闭区间,此时确定middle对应的数字在target左侧
                # 设置left=middle+1确保新的左区间是从target可能的取值开始
            elif nums[middle]>target:
                right=middle-1
                # right为闭区间,此时确定middle对应的数字在target右侧
                # 设置right=middle-1确保新的右区间是从target可能的取值开始
            else: return middle # middle对应的数字就是target
        return -1 # 数组中不存在target

左闭右开

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left,right=0,len(nums)
        # 由于left<right,要确保right是第一个取不到的数字
        # 因此right初始值要设为List的最后一位元素的索引值+1
        while(left<right): # [left,right)左闭右开
            middle=left+(right-left)//2 
            #等同于(left+right)/2,目的是防止内存溢出
            if nums[middle]<target:
                left=middle+1
                # left为闭区间,此时确定middle对应的数字在target左侧
                # 设置left=middle+1确保新的左区间是从target可能的取值开始
            elif nums[middle]>target:
                right=middle
                # right变为开区间,此时middle对应的数字在target右侧
                # 新的右区间要确保包含了所有target可能的取值
                # 且是第一个取不到的数字,即right=middle
            else: return middle # middle对应的数字就是target
        return -1 # 数组中不存在target

拓展题目

两道拓展题目已经通过博客记录了,附上Leetcode 34和Leetcode 35的博客链接
Leetcode 34 博客记录链接
Leetcode 35 博客记录链接


27. 移除元素

Leetcode 27. 移除元素

B站视频对应讲解,简单易懂

Python Remove

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        while val in nums:
            nums.remove(val)
        return len(nums)         

Python中的remove可以使这道题的解答不那么复杂。但本质上python中的remove是针对list的库函数,而list不是数组,数组存储的是相同类型的数据, list可以存储不同类型的数据,list是更高级的数据结构了。

如果库函数可以直接解决一道题,那么最好练习算法时不要用库函数;当其只是其中一小步的时候,可以用库函数简化过程。

本质上Q27需要将其视为数组来解决,而数组在内存中是连续的地址空间,数组是不能删除单一元素的,只能覆盖,删除只能是全部删除(程序运行结束,回收内存站空间)。因此,remove虽然也能解决这个问题,但并不是从数组的角度,即通过覆盖来实现。

暴力解法

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        i=0
        l=len(nums)
        while i<l: 
        # 不用for是因为当移除目标元素后,i应当-1
        # 但for不会使用-1后的i,还会用原来的i
        # 因此用while循环i+=1
            if nums[i]==val:
                for x in range(i+1,l): 
                # 当i+1=l时,range返回一个空集,不会进入for循环
                # i+1=l表明最后一个元素=val,不用覆盖,直接nums长度-1
                    nums[x-1]=nums[x]
                    #后续的元素往前移动一格
                i-=1
                l-=1
            i+=1
        return l # 返回最后的数组长度

这个算法的时间复杂度为O(n),其中n是输入列表nums的长度。算法中的主要操作是遍历列表中的元素,其时间复杂度为O(n)。在最坏的情况下,需要遍历整个列表。

双指针法

通过设置两个指针,一个快指针在原有数组探路,寻找新数组的元素(即不等于val的值),一个慢指针指向更新新数组下标的位置。

实际看作慢指针指向的是一个空的虚拟数组,快指针传递数值过来,从头开始覆盖虚拟数组上的位置。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        slow,fast=0,0 # 初始化快慢指针
        l=len(nums) 
        while fast<l: # 不加等于是因为,fast = l 时,nums[fast] 会越界
        # slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它与 slow 替换
            if nums[fast]!=val: 
                nums[slow]=nums[fast]
                slow+=1 #收集到不等于val的值时,slow+=1等待下一个不等于val的值
            fast+=1 # fast+=1往前探索
        return slow # 返回最终slow停的地方就是新数组的长度

这个算法的时间复杂度为O(n),其中n是输入列表nums的长度。算法使用了双指针的方法,通过一次遍历将不等于val的元素移到数组的前面,并返回最终的元素个数。

在最坏的情况下,需要遍历整个列表,即执行n次循环。每次循环中,只有在nums[fast]不等于val时才会执行元素的替换操作,并将slow指针后移。因此,每个元素最多被访问和替换一次。

因此,总体的时间复杂度为O(n)

双指针法优化

由于题目允许:元素的顺序可以改变。那么,相比于把后续的元素都往前移动一格,我们可以直接用末尾的元素覆盖掉当前元素,避免移动全部数据的同时,还能减少之后检索的数组长度。这个优化在序列中val 元素的数量较少时非常有效。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        left=0 
        right=len(nums)
        # 设置左右两个区间
        while left<right: # 当left=right时,nums[left]会越界
            if nums[left]==val:
            # 检索到val就用末尾元素覆盖掉他
                nums[left]=nums[right-1] #末尾元素索引为right-1
                right-=1 # 缩小区间
            else:left+=1 
            # 没检索到val就直接跳过,不对其操作
            # 没有else会导致:当首尾元素都是val情况时,末位元素覆盖掉当前元素后,由于left+1,直接跳到后续元素,导致更新后的当前元素没有被去除
        return left # left为最后新数组的长度

双指针法总结

双指针法相比于暴力解法,优点在于慢指针对应的虚空数组,形象理解起来可以看作往里面填数就可以了,解法清楚又简单。而暴力解法的覆盖,虽然很清楚,但不好脑补,容易搞晕。


  • 34
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二天的算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值