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

今日碎碎念:从今天开始,正式开启连续刷题的日子啦!!坚持就能胜利!

        开始今日的刷题内容:


704. 二分查找

题目链接:704.二分查找

文章讲解:代码随想录数组2.二分查找

视频链接:手把手教你手撕正确的二分法|二分查找法|二分搜索法

状态:没有思路,做不出来

 

题目:

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例1:

输入: nums = [-1,0,3,5,9,12], target = 9     
输出: 4       
解释: 9 出现在 nums 中并且下标为 4  

示例2:

输入: nums = [-1,0,3,5,9,12], target = 2     
输出: -1        
解释: 2 不存在 nums 中因此返回 -1

思路:

根据题意,数组为有序的升序整型数组,并且没有重复的元素,因此可以考虑使用二分法来进行求解。

 

算法:二分法

        1. 定义:

        在一组有序数组中查找target目标值,如果target在这组有序数组中,则返回target在数组中的下标。

        算法思路如其名,就是将一组数组依次一分为两个区间,然后逐个比较其边界条件来最终确定目标值的所在区间。

        2. 注意事项  
  • 二分法的使用前提是数组有序并且没有重复的元素。只有这样,使用二分法返回的下标才具有唯一性。

  • 尝试用左闭右闭、左闭右开两种方式来定义查询区间。

  • 一定要先确定好查找区间,避免在代码编写过程中随意改变(循环不变量原则)

  • 关键:根据区间的定义来写代码

题解: 

  • 方法1:使用左闭右闭的方式来定义查找区间

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums)-1   # 确定好搜索的边界,这里是一个左闭右闭的搜索区间。确定好target是在这个区间内的
        while left <= right:
            middle = (left + right) // 2
            if nums[middle] > target:
                right = middle - 1    #target在左边的区间,所以要更新右边界。由于是左闭右闭的区间,middle已经属于上一个区间内了,所以减一后才是新的左闭右闭区间
            elif nums[middle] < target:
                left = middle + 1  
            else:
                return middle

        return -1  # 在有序数组中没有这样的值,返回-1
  •  方法2:使用左闭右开的方式来定义查找区间(运行速度很慢,不推荐)
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left, right = 0, len(nums)   # 确定好搜索的边界,这里是一个左闭右开的搜索区间,确定好target在搜索区间范围内。因为不包含右边的值,所以不需要减一来获取实际的数值位置
        while left < right:   # 这里的定义要符合左闭右开区间的定义。[1,1),如果取等号,则不符合区间的定义。
            middle = (left + right) // 2
            if nums[middle] > target:
                right = middle    #target在左边的区间,所以要更新右边界。由于是左闭右闭的区间,middle已经属于上一个区间内了,所以减一后才是新的左闭右闭区间
            elif nums[middle] < target:
                left = middle + 1  # 更新左边界,由于左边是闭区间,则需要加1才能不包含上一区间 
            else:
                return middle

        return -1  # 在有序数组中没有这样的值,返回-1


27. 移除元素

题目链接:27.移除元素

文章讲解:代码随想录数组3.移除元素

视频链接:数组中移除元素

状态:没有思路,做不出来

 

题目:

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

思路:

根据数据结构的知识,数组这个数据类型在系统中的存储地址是连续的,不能单独删除只能进行覆盖。同时根据题意,需要在原地移除所有数值,并不能使用额外的数组空间,即仅使用O(1)的空间来使用数组。因此可以有两种方法来进行求解:①暴力解法,时间复杂度O(n^2);②双指针法(快慢指针法)

那什么是快慢指针法?

算法:双指针法

(1)简介:

在遍历元素的过程中,不是使用单个指针进行访问(暴力解法),而是使用两个指针进行访问。定义一个快指针和一个慢指针分别指向不同的元素,通过对比指向的元素来完成任务。

  • 指针名称定义:

    • 对撞指针:两个指针的方向相反

    • 快慢指针:两个指针方向相同

    • 分离双指针:两个指针分别属于不同的数组/链表

  • 不同问题选用不同的指针:

    • 区间搜索问题:滑动窗口。两个指针指向同一数组,遍历方向相同且不会相交,则称为滑动窗口。

    • 搜索有序数组(需要提前进行排序):对撞指针。两个指针指向同一数组,遍历方向相反。

(2)对撞指针

两个指针left、right分别指向序列第一个元素和最后一个元素,left指针不断递增,right指针不断递减,直到两个指针的值相等(left == right),或者满足其他要求的特殊条件为止。

1)对撞指针的求解步骤
  • 使用两个指针left、right。left指向序列的第一个元素,即:left = 0, right指向序列的最后一个元素,即:right = len(nums) - 1。

  • 在循环体中将左右指针相向移动,当满足一定条件时,将左指针右移,left += 1。当满足另外一定条件时,将右指针左移,right -= 1。

  • 直到两指针相撞(即left == right),或者满足其他要求的特殊条件时,跳出循环

2) 对撞指针适用范围

对撞指针一般用来解决有序数组或者字符串问题:

  • 查找有序数组中满足某些约束条件的一组元素问题:比如二分查找、数字之和等问题。

  • 字符串反转问题:反转字符串、回文数、颠倒二进制等问题。

(3)快慢指针

指两个指针从同一侧开始遍历序列,且移动的步长一个快一个慢。移动快的指针被称为快指针(fast),移动慢的指针被称为慢指针(slow)。两个指针以不同速度、不同策略移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时为止。

1) 快慢指针的求解步骤
  • 使用两个指针slow、fast。slow一般指向序列第一个元素,即:slow = 0,fast一般指向序列的第二个元素,即:fast = 1

  • 在循环体中将左右指针向左移动。当满足一定条件时,将慢指针右移,即 slow += 1。当满足另外一定条件时(也可能不需要满足条件),将快慢指针右移,即fast += 1。

  • 到快指针移动到数组尾端(即fast == len(nums) - 1),或者两指针相交,或者满足其他的特殊条件时跳出循环体

2)快慢指针的适用范围

快慢指针一般用于处理数组中的移动、删除元素问题,或者链表中的判断是否有环、长度问题。

(4)分离双指针

两个指针分别属于不同的数组/链表,两个指针分别在两个数组/链表中移动

1)分离双指针的求解步骤
  • 使用两个指针left_1、left_2。left_1指向第一个数组/链表的第一个元素,即:left_1 = 0,left_2指向第二个数组/链表的第一个元素,即:left_2 = 0。

  • 当满足一定条件时,两个指针同时右移,即left_1 += 1、left_2 += 1。

  • 当满足另外一定条件时,将left_1指针右移,即left_1 += 1。

  • 当满足其他一定条件时,将left_2指针右移,即left_2 += 1。

  • 当其中一个数组/链表遍历完时,或者满足其他特殊条件时,跳出循环体。

2) 分离双指针适用范围

一般用于处理有序数组合并、求交集、并集问题。

题解:

  • 双指针法(快慢指针)

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        # 定义快慢指针
        fast, slow = 0, 0  #两个指针都从零开始进行遍历

        size = len(nums)  #在这个范围内是这一组数组的长度,不包含边界
        while fast < size:  #不加等于是预防数据越界
            # slow 用来收集不等于val的值,如果fast对应值不等于val,则把他与slow替换
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
            fast += 1

        return slow
  • 暴力解法:(速度比上一个方法快很多)

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        i, l = 0, len(nums)  #从第一个位置开始遍历,遍历的长度是l=数组的长度
        while i < l:
            if nums[i] == val:  #如果找到了目标值的节点
                for j in range(i+1, l): # 移除该元素,并将后面元素向前平移
                    nums[j - 1] = nums[j]

                l -= 1
                i -= 1
            i += 1
        return l
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值