【LeetCode刷题之路:数组一】


前言

看完《算法图解》一书后,需要算法进阶与大量实践,目前准备先按照网上大佬推荐的LeetCode刷题顺序进行实战,此博客仅作学习过程的记录。

一、二分查找

LeetCode第704题:

(1)暴力求解

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        for i in range(len(nums)):
            if target == nums[i]:
                return i
        else:
            return -1

结果:
在这里插入图片描述
时间复杂度为O(n)。

(2)二分查找

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        n = len(nums)
        left = 0
        right = n -1
        while left <= right:
            middle = (left + right) // 2
            if nums[middle] < target:
                left = middle + 1
            elif nums[middle] > target: #一定要用elif,否则下面的else只与这一个联系
                right = middle - 1
            else:
                return middle
        return -1         

这里使用的是二分查找中,左闭右闭的类型,在while判断中有等于符号,在left、right迭代中,需要+1、-1.

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        n = len(nums)
        left = 0
        right = n -1
        while left < right:
            middle = (left + right) // 2
            if nums[middle] < target:
                left = middle + 1
            elif nums[middle] > target: #一定要用elif,否则下面的else只与这一个联系
                right = middle
            else:
                return middle
        return -1         

这里使用的是左闭右开的写法,在提交的时候报错了,应该是只有列表中只有一个元素的会出现判断错误,可能需要单独考虑一个元素时的问题。


LeetCode第35题:

(1)暴力求解:

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        for i in range(len(nums)):
            if nums[i] >= target:
                return i
        return len(nums)

在这里插入图片描述
这种解法不满足题目O(logn)的要求,但是很好用。

(2)二分查找

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        # O(logn)复杂度,采用二分查找
        # 相比经典的二分查找,需要解决在查找不到是的情况
        # 思路:先解决一般查找情况
        # 左闭右闭写法
        left = 0
        n = len(nums)
        right = n - 1
        while(left <= right):
            middle = (left + right) // 2 #避免小数
            if nums[middle] > target:
                right = middle - 1
            elif nums[middle] < target:
                left = middle + 1
            else:
                return middle
        return right + 1

考虑四种情况:
a.target存在与数组中
b.target在数组左端
c.target在数组右端
d.target在数组中间
第一种按照return middle处理,其余三种经过分析都可以按照right + 1处理。


LeetCode第34题:

在这里插入图片描述

(1)暴力求解:

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        # 仍然先采用暴力求解:
        res = [] #创建用于储存的结果的数组
        for i in range(len(nums)):
            if target == nums[i]:
                res.append(i)
        if len(res) > 0:
            return [res[0],res[-1]]
        else:
            return [-1,-1]

虽然不满足题目要求,但是直观、简单。

(2)二分查找

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        def find_left(nums,target):
            left = 0
            right = len(nums) - 1
            leftboard = -2
            while(left <= right):
                middle = (left + right) // 2
                if nums[middle] < target:
                    left = middle + 1
                    # leftboard = left
                else:
                    right = middle - 1
                    leftboard = right + 1
# 使用right赋值左边界的理由:比如当只有一个元素时,可能求不了if的情况,而只有else的情况,这时不会执行if语句,也就是说leftboard保持初始值,而会执行else语句,因为else包含了=的情况,所以这一步操作是为了处理这一类特殊的问题。且right = left - 1
            return leftboard

        def find_right(nums,target):
            left = 0
            right = len(nums) - 1
            rightboard = -2
            while(left <= right):
                middle = (left + right) // 2
                if nums[middle] > target:
                    right = middle - 1
                    # rightboard = right
                else:
                    left = middle + 1
                    rightboard = left - 1
            return rightboard

        leftboard = find_left(nums,target)
        rightboard = find_right(nums,target)
        if leftboard == -2 or rightboard == -2:
            return [-1,-1]
        if rightboard >= leftboard:
            return [leftboard,rightboard]
        return [-1,-1]            

这里写了很久,最难处理的是二分法的边界问题,以及考虑到各种奇怪的输入,中间需要注意的主要有两点:
a.当顺序数组中存在相同元素的时候,查找该元素时,经典二分法返回的结果只会是其中的一个。所以当考虑到有重复元素时,需要将查找定值问题转换为查找边界问题,而我总结到的规律时,当要查找左边界时,在if语句中对左边界的操作不变,而将>=target的情况合并为右边界操作。查找右边界类似(左闭右闭法)。最终的left为第一个=target的元素的下标,而right = left - 1。
b.第二个需要注意的点时,边界值给定后修改的问题,见代码中的注释。


二、删除元素

LeetCode第27题

在这里插入图片描述

(1)暴力求解

    def removeElement(self, nums: List[int], val: int) -> int:
        n = len(nums)
        i, j = 0, 0
        while i < n:
            if nums[i] == val:
                while j < (n - i - 1):
 #这里比较容易绕进去,易将索引值与元素数弄混,这里右边代表的时元素数量,所以 = n - (i + 1) = n - i - 1
                    nums[i + j] = nums[i + j + 1]
                    j = j + 1
                i = i - 1
                n = n - 1
                j = 0      # 每次循环完j的值记得归零
            i = i + 1
        return n

在这里插入图片描述

两个循环的暴力求解法,这里在使用Python写的时候出现了一点问题,不像C++或者Java中的for循环很容易能够在循环中改变循环的范围,Python中采用range(n)的话相当于生成了一个新的列表,即使在循环中改变n的值也不会使得循环次数改变,所以这里采用了while循环,但是一定要将边界考虑情况,不然容易出现循环不完整或者循环超出范围的问题。


(2)双指针

暴力求解虽然能够通过,但是时间复杂度为O(n**2),那么有没有一种解法能够将复杂度降低为O(n)呢?答案是双指针,代码如下:

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        SlowIndex = 0
        n = len(nums)
        for FastIndex in range(n):
            if nums[FastIndex] != val:
                nums[SlowIndex] = nums[FastIndex]
                SlowIndex = SlowIndex + 1
        return SlowIndex

双指针的思想感觉上是将一个数组当两个数组来用,而使用的必要条件就是两个指针对数组的调用不会相互干扰,因此才有了快慢指针一说,在这里,快指针的作用时查找元素,即找到新数组中的元素,慢指针的作用是更新数组,当查找到不为val的元素时,进行储存。实际上这与最为直观的思路保持一致,遍历整个数组,找到不为val的元素并将其储存在新的数组中,只是这样使得时间复杂度与空间复杂度都为N,那么为了降低空间复杂度,不开辟新的空间,直接在原数组中操作,就变为了现在的双指针的思想。


总结

这次就更新这么多内容,后续将一直记录与更新。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值