代码随想录算法训练营第一天

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

704. 二分查找

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

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

左闭右开还是左闭右闭?

两种做法其实都可以,一开始选定一种做法之后就需要连贯下去保持不变,关于怎么确定边界则取决于现在选择的区间是否合法。

左闭右闭

左闭右开:[left,right]
nums[right]这个元素其实是包括在这个区间内的,且当left==right时,这个区间依然是合法的,所以之后的操作都要注意这一点。

class Solution(object):
    def search(self, nums, target):
    	"""
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
    	#左闭右闭:[left,right]
        left = 0
        right = len(nums)-1 #right在搜索区间里,注意nums[length(nums)]其实是不存在的
        while left <= right: #left==right区间依然合法所以可以写等于
            middle = int((left+right)/2) #记得加上int
            if nums[middle] > target:
                right = middle - 1 #right在搜索区间里,但是nums[middle]已经确定不是target了所以不要
            elif nums[middle] < target:
                left = middle + 1 #一样,确定不是了所以不要
            else:
                return middle
        return -1

左闭右开

左闭右开:[left,right)
nums[right]这个元素是不包括在这个区间内的,且当left==right时,这个区间是不合法的。

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        #左闭右开:[left,right)
        left = 0
        right = len(nums) #right不在搜索区间里,所以可以取到len(nums)
        while left < right: #因为取不到,所以left==right时,[left,left)是不合法的所以不可以等于
            middle = int((left+right)/2)
            if nums[middle] > target:
                right = middle #本来就取不到所以不用-1
            elif nums[middle] < target:
                left = middle + 1 #取得到所以要+1
            else:
                return middle
        return -1

总结

不管是哪种方法注意前后一致。

27. 移除元素

题目链接:移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(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],
     也会被视作正确答案。

数组的删除并不是直接就拿掉这个元素,其实是覆盖。删完之后会返回删掉之后的size但实际的物理空间其实还是原来的长度,最后几个没覆盖的并没有被处理。
list.remove()移除函数其实是个O(n)的函数,删除掉一个元素然后后面的元素整体向前移一个位置。
如果一道题可以直接用库函数解决,那就不要用,如果要使用也要知道这个库函数的时间复杂度

暴力解法

暴力解法需要两个循环,一个用来遍历整个数组,另一个用来更新数组(把要移除的元素后面的元素都往前移一位),这种做法的时间复杂度是O(n2)。
这里要注意是当找到了val然后把后面的元素都往前移一位之后,此时的i依然是原先val位置的i不需要往后走一位,因为后面那一位已经移上来了。

class Solution(object):
    def removeElement(self, nums, val):
        """
        :type nums: List[int]
        :type val: int
        :rtype: int
        """
        #暴力解法
        l = len(nums)
        i = 0
        while i < l:
            #print(nums[i], val)
            if nums[i] == val:
                for j in range((i+1),l):
                    nums[j-1] = nums[j]
                l = l - 1
            else: #需要一个else,if成立的情况下i是不需要+1的 
                i = i + 1
        return l

双指针

可以用O(n)来实现这个问题。有一个快指针和一个漫指针,快指针用来找到新数组的元素,慢指针代表我们需要更新的下标,当快指针找到一个非val元素就通知慢指针去覆盖,这里其实是遍历和更新在同时进行。
val = 3, nums = [1, 3, 4, 3, 5] slow=0, fast=0
同时指向元素1,不是val,slow=1, fast=1
同时指向元素3,是val,不用更新,slow=1, fast=2
slow还指向元素3,fast指向元素4,不是val,更新,slow=2, fast=3, nums = [1, 4, 4, 3, 5]
slow还指向元素4,fast指向元素3,是val,不用更新,slow=2, fast=4
slow还指向元素4,fast指向元素5,不是val,更新,slow=3, fast=5结束
nums=[1, 4, 5, 3, 5],size = slow = 3。

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        #双指针解法
        fast = 0 #指向新数组的元素
        slow = 0 #代表需要更新的下标
        while fast < len(nums):
            if nums[fast] != val:
                nums[slow] = nums[fast]
                fast += 1
                slow += 1
            else:
                fast += 1
        return slow

总结

双指针快指针用于遍历,慢指针用于更新,只用一个循环,所以复杂度是O(n)。

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

题目链接:在排序数组中查找元素的第一个和最后一个位置
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

这个题和704很相似,一开始就觉得应该用二分法来做,但是想了很久都不知道怎么来处理前后是否也是target的方法,一直都想能不能一步到位,但还是没有做到后来还是选择了先写了二分查找的函数再找前后,这个应该不是O(log n)了,应该是O(n/2)

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        #[left, right)
        def searchtarget(nums: List[int], target: int) -> int:
            left = 0
            right = len(nums) 
            while left < right:
                middle = int((left+right)/2)
                if nums[middle] < target:
                    left = middle + 1
                elif nums[middle] > target:
                    right = middle
                else:
                    return middle
            return -1
        
        index = searchtarget(nums, target)
        if index == -1: return [-1,-1]
        else:
            left = index
            right = index
        #这个地方是不是out of range 一定要写在是不是等于target之前,超过了就nums[left - 1]和nums[right + 1]就走不了
        while left - 1 >= 0 and nums[left - 1] == target:
            left -= 1
        while right + 1 < len(nums) and nums[right + 1] == target:
            right += 1
        return [left, right]
        

O(log n)的做法可以先用二分法找到左右边界再往下做,写的时候搞混了好几次,所以简单记录一下几种情况,首先左右边界其实就是距离target最近的左右位置,如果target 在nums里那就是target所在的位置的左右位置。以下是四种情况:
nums = [3,5,7]

  1. taget = 2,左边界不存在右边界存在,(-2, 0)
  2. taget = 8,左边界存在右边界不存在,(2, -2)
    理清这两种情况可以帮助写左右边界的function
  3. taget = 4,左边界存在右边界存在,(0, 1),target不存在,这种情况下因为nums是有序的,target一定会卡在两个相邻的位置之间,所以只要左右边界相邻那就一定是target不存在。
  4. taget = 5,左边界存在右边界存在,(0, 2),target存在,这种情况下target有自己的位置所以左右边界也是隔开的,左边界往前一位,右边界往后一位就可以找到target所在的位置。
class Solution(object):
    def searchRange(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        def rightrange(nums, target):
            # [left, right)
            left = 0
            right = len(nums) 
            r = -2
            while left < right:
                middle = int((left+right)/2)
                if nums[middle] <= target:
                    left = middle + 1
                    r = left
                else:
                    right = middle
            return r
        
        def leftrange(nums, target):
            # [left, right)
            left = 0
            right = len(nums) 
            l = -2
            while left < right:
                middle = int((left+right)/2)
                if nums[middle] < target:
                    left = middle + 1
                else:
                    right = middle
                    l = right - 1
            return l

        l = leftrange(nums, target)
        r = rightrange(nums, target)
        if (l == -2) or (r == -2): return [-1, -1]
        if r - l > 1: return [l+1, r-1]
        return [-1,-1]

35. 搜索插入位置

题目链接:搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。

输入: nums = [1,3,5,6], target = 5
输出: 2
输入: nums = [1,3,5,6], target = 7
输出: 4

也是用二分法来做,我用的是左闭右开,这个地方我插入了一个index用来记录target应该在位置。如果最后能找到target的位置就不用index,如果找不到就输出index。
index记录方法是如果区间的中位数是小于target的那么target的位置应该在中位数之后;如果区间的中位数是大于target的那么target的位置应该在中位数之前,每次循环都可以做一个更新直到找到target或者循环结束。

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        #[left, right)
        left = 0
        right = len(nums)
        index = 0
        while left < right:
            middle = int((left + right)/2)
            if nums[middle] > target:
                right = middle
                index = middle
            elif nums[middle] < target:
                left = middle + 1
                index = middle + 1
            else:
                return middle
            
        return index

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值