Leetcode数组篇

目录

数组基础知识

  1. 数组是存放在连续内存空间上的相同类型数据的集合。(二维也是连续存储空间);
    在这里插入图片描述​​​​
  2. 数组的元素是不能删的,只能覆盖
  3. 优缺点:
    优:查、改元素很容易 时间复杂度O(1)
    缺:插入、删除元素需要移动大量元素 时间复杂度O(n);
    大小固定,只能存储一种类型的数据;

基本题

1-704 二分查找

题目

在这里插入图片描述

分析

有序数组(以升序为例),将中间位置middle的值与target比较,若相等则直接return;若小于target,则说明middle以左包括middle位置的所有值均比target小,只要研究右边部分,改变搜索的左边界,继续执行操作;同理,若大于target,则仅需研究左半,改变搜索的右边界;

易错点

边界的修改
while(left < right) 还是 while(left <= right),到底是right = middle呢,还是要right = middle - 1
while内条件要满足使得区间有意义

代码
# 左闭右开[)
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums)
        while left < right:  # 区间合法 则一直进行
            mid = left + (right - left)//2
            # 不用(left + right)//2 是为了防止两个32位的整数相加溢出
            # //2是为了向下取整 防止小数  和c++中不同 int/int还是int
            if nums[mid]>target:
                right = mid  # 新的搜索区间为[left,mid-1]  由于右开  可直接令right=mid
            elif nums[mid]<target:
                left = mid+1
            else:
                return mid
        return -1
 # 左闭右闭[]
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums)-1
        while left<=right:
            mid = left+(right-left)//2
            if nums[mid]<target:
                left = mid+1
            elif nums[mid]>target:
                right = mid-1
            else:
                return mid
        return -1
小结

二分法适合范围:升序数组
while 条件;左右区间更改;mid更新防溢出
二分法时间复杂度O(logN)

33. 搜索旋转排序数组(二分法进阶)

在这里插入图片描述

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        l = 0
        r = len(nums)-1
        while l<=r:
            mid = l + (r-l)//2
            if nums[mid] == target:
                return mid
            if nums[0]<= nums[mid]:  # 说明mid落在左半 0-mid一定都是有序的
                if nums[0]<=target<nums[mid]:  # 就转化为有序数组nums[0]-nums[mid]二分法找目标值
                    r = mid -1
                else:  # 落在右半  无序
                    l = mid + 1
            else:  # 说明mid落在右半 mid-末尾一定是有序的
                if nums[mid] < target <= nums[len(nums)-1]:
                    l = mid+ 1
                else:
                    r = mid -1
        return -1 # nums中不存在目标值,返回-1

34. 在排序数组中查找元素的第一个和最后一个位置(二分法进阶)

在这里插入图片描述
思路:两次使用二分法分别找到左边界和右边界

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        # 使用两次二分法找到左右边界

        def searchBound(nums, target, lor):
            res = -1
            l = 0
            r = len(nums) - 1
            while l <= r:
                mid = l + (r - l) // 2
                if nums[mid] < target:
                    l = mid + 1
                elif nums[mid] > target:
                    r = mid - 1
                else:
                    res = mid  # 记录当前边界
                    if lor == 0:  # 并继续更新边界
                        r = mid - 1
                    else:
                        l = mid + 1
            return res    


        left = searchBound(nums, target, 0)  # 左边界
        right = searchBound(nums, target, 1)  # 右边界
        return [left, right]

4. 寻找两个正序数组的中位数

在这里插入图片描述
k = m + n
k为奇:找第(k+1)//2个数
k为偶:找第k//2和第k//2+ 1个数

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        def getKthElement(k):  # 找到第k小的元素
            index1, index2 = 0, 0
            while True:
                if index1 == n1:  # 越过nums1
                    return nums2[index2 + k-1]
                if index2 == n2:
                    return nums1[index1 + k-1]
                if  k == 1:  # 终止条件
                    return min(nums1[index1], nums2[index2])

                newIndex1 = min(index1 + k//2-1, n1-1)
                newIndex2 = min(index2 + k//2-1, n2-1)
                if nums1[newIndex1]<=nums2[newIndex2]:
                    k = k - (newIndex1-index1+1)
                    index1 = newIndex1 + 1
                else:
                    k = k - (newIndex2-index2+1)
                    index2 = newIndex2 + 1


        n1 = len(nums1)
        n2 = len(nums2)
        m = n1+n2
        if m%2==1:  # 奇数
            return getKthElement((m+1)//2)
        else:
            return (getKthElement(m//2) + getKthElement(m//2+1))/2

1-27 移除元素

题目

在这里插入图片描述
在这里插入图片描述

分析

由于vector内存的连续性,是不能直接对某个元素直接删除的,而是需要用其他元素对其进行覆盖,表现出删除的效果。
本题本质是实现vector的erase操作,注意erase也是对数据进行覆盖实现的,一次erase操作的时间复杂度为O(n)

方法
法1 暴力解

通过两个for循环实现:
第一个for循环对数组遍历
当第一个for循环遇到与val相等的值时,使用第二个for循环将后面的数组前移一位
注意:前移后需从第一层循环的当前位置开始遍历,而不是后一个,放出几个val相连的情况出现

法2 快慢指针(同起点、首尾双起点)

使用双指针要明确指针的含义
快:找到新数组需要的元素(即不等于val的元素)
慢:新数组索引

代码
# 暴力解法
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:  
        size = len(nums)
        i = 0 
        while i < size:  # size随着前移在变化
            if nums[i] == val:
                for j in range(i+1,size):
                    nums[j-1] = nums[j]
                i -= 1
                size -= 1
            i += 1
        return i
# 双指针(头开始) ---好处:不改变原数组的元素顺序
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int: 
        n = len(nums)
        p1 = 0  # 新数组索引
        p2 = 0  # 找到新数组中的元素
        while p2<n:
            if nums[p2] != val:
                nums[p1] = nums[p2]
                p1 += 1
            p2 += 1
        return p1
# 双指针(首尾)
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int: 
        n = len(nums)
        left = 0
        right = n-1
        while left<=right:
            while left<=right and nums[left] != val:
                left += 1  # 右指针所处的位置不等于val
            while left<=right and nums[right] == val:
                right -= 1  # 左指针所处的位置等于val
            if left < right:  # 经过上面两个while left和right不可能在同一位置
                nums[left] = nums[right]
                left += 1
                right -= 1
        return left
易错点

使用暴力解法时循环的索引位的变化

小结

双指针(头开始)——不改变原数组的元素顺序
双指针(首尾)——两指针合起来只遍历了数组一次。与方法二不同的是,两端开始避免了需要保留的元素的重复赋值操作。

283. 移动零

在这里插入图片描述

class Solution:
    def moveZeroes(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        n = len(nums)
        j = 0
        for i in range(n):  
            if j == n:
                nums[i:]=[0]*(n-i)    
                break          
            while j+1<n and nums[j]==0:
                j += 1   # j指向不为0的元素
            nums[i] = nums[j] 
            j += 1


977 有序数组的平方

题目

在这里插入图片描述

分析

特点: 已排序好的数组,考虑的问题就是负数平方之后可能成为最大数。

数组平方的最大值就在数组的两端,不是最左边就是最右边,由两边向中间减小。

此时可以考虑双指针法了,左指向起始位置,右指向终止位置。哪个指针对应的元素大就把这个值赋给新数组,指针移动。

方法
法1 暴力法

每个数平方之后,排个序,时:O(nlogn) 空:O(1)

法2 双指针法

时:O(n) 空:O(n)

代码
class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        n = len(nums)
        for i in range(n):
            nums[i] = nums[i]**2 # 平方的三种写法 num*num  num**2  math.pow(num,2) 
        return sorted(nums)
class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        n = len(nums)
        nums2 = [None]*n  # 初始化数组大小
        left = 0
        right = n-1
        for i in range(n-1,-1,-1):
            lm = nums[left]**2
            rm = nums[right]**2
            if lm < rm:  # 右指针对应的元素更大 
                nums2[i] = rm
                right -= 1
            else:  
                nums2[i] = lm
                left += 1
        return nums2  
代码小点

1、平方的三种写法:num*num; num**2; math.pow(num,2)
2、数组初始化:
若数组是追加一个元素的可以不用定义长度;a=[]; a.append(i)
若初始化一个列表然后要修改其中的值的话,就要定义长度了。b = [None]*size



448. 找到所有数组中消失的数字

在这里插入图片描述

class Solution:
    def findDisappearedNumbers(self, nums: List[int]) -> List[int]:
        # 遍历nums 找到数组中nums[i]-1的元素,将其值取为负的  负的表示这个索引对应的元素+1 出现过(鸽笼)
        n = len(nums)
        ans = []
        for i in range(n):
            nums[abs(nums[i])-1] = -abs(nums[abs(nums[i])-1])

        for i in range(n):
            if nums[i]>0:
                ans.append(i+1)
        return ans

209 长度最小的子数组

题目

在这里插入图片描述

分析

要找到一段区间,需要起始和终止位置,由于也要考虑区间内部,称为滑动窗口法
关键:大于目标值缩小窗口,小于目标值扩大窗口。滑动窗口for循环中是窗口终止

方法
滑动窗口法 时:O(n)
代码
class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        # 滑动窗口
        n = len(nums)
        j = 0  # 窗口起始
        res = n+1  # 返回的窗口大小
        s = 0  # 窗口内数据和
        for i in range(n):  # 窗口终止右移  窗口变大
            s = s + nums[i]
            while s >= target:
                lenth = i-j+1 # 满足要求的长度
                res = min(lenth,res)
                s = s - nums[j]  # 减去窗口起始处的值
                j += 1 # 窗口起始右移 窗口缩小
        if res <= n:
            return res  
        else:
            return 0


59 螺旋矩阵II

题目

在这里插入图片描述在这里插入图片描述

分析

就是若干圈循环,共有n//2圈,奇数圈最中间的结点单独赋值;
每一圈最好保证循环/遍历条件同,否则会加大分析难度
以3*3为例,最外面一圈,每n-1个一次循环;下一圈每n-3个一循环,所以每圈结束后要有个偏移量offset。
综上:外循环(圈数);内循环(行列四个方向对矩阵元素赋值)
注意for i in range(3),i最终为2;while i<3,最终i=3 有差别

代码
class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        a = [[None]*n for _ in range(n)]  # n*n矩阵
        offset = 1
        i = 0
        count  = 1 
        for cir in range(n//2):  # 注意到每圈的起始行列位置相等
            for j in range(cir,n-offset):  # 左往右
                a[i][j] = count
                count += 1
            for i in range(cir,n-offset):  # 上往下
                a[i][j+1] = count
                count += 1
            for j in range(n-offset,cir,-1):  # 右往左
                a[i+1][j] = count
                count += 1
            for i in range(n-offset,cir,-1):  # 下往上
                a[i][j-1] = count
                count +=1
            offset += 1
        if n %2 ==1:  # n时奇数 对中间的最后一个赋值
            a[n//2][n//2] = n**2
        return a

*** 代码易错处:预设矩阵——[[]*n for _ in range(n)](×)
*** cir 本身也可以作为偏移量

238. 除自身以外数组的乘积

在这里插入图片描述
思路:两个数组分别对应当前位置的前缀积和后缀积,则res[i]等于前缀积和后缀积的乘积

class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        ans = [0]*n
        # prefix product 前缀积
        prepd = [1]*n
        # suffix product 后缀积
        supd = [1]*n

        for i in range(1, n):
            prepd[i] = prepd[i-1]*nums[i-1] 
            supd[n-1-i] = supd[n-i]*nums[n-i]
        for i in range(n):
            ans[i] = prepd[i]*supd[i]  # i位置的结果等于前缀积和后缀积的乘积
        return ans

方法总结

二分法

双指针法

滑动窗口

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值