算法基础——数组

本文介绍了数组的基本概念、内存管理(如Python和C++中的内存连续性),并详细讨论了二分查找、移除元素、有序数组操作(如平方和最小子数组)、双指针技巧以及Timsort排序算法在这些问题中的应用。还涉及Python内存管理和垃圾回收机制。
摘要由CSDN通过智能技术生成

算法基础——数组


1、预备

  • 数组是存放在连续内存空间上的相同类型数据的集合,采用下标索引。
  • 二维数组在内存空间是否连续取决于编程使用的语言。在python中可以通过id(),在c++中使用&取地址符进行观察。如在c++中0x7ffee4065820 0x7ffee4065824 0x7ffee4065828 0x7ffee406582c 0x7ffee4065830 0x7ffee4065834是int型占4字节,16进制的连续地址。说明二维数组在c++上是连续的。Python的解释器在执行程序时会负责虚拟内存地址的寻址操作,将程序中的变量、对象等分配到内存中的不同位置,并管理这些内存地址的分配和释放。Python的解释器使用了一种称为"引用计数"的机制来管理内存。每个对象都有一个引用计数,表示有多少个变量引用了该对象。当引用计数为0时,对象就会被垃圾回收。除了引用计数,Python的解释器还使用了其他的垃圾回收机制,如标记清除、分代回收等,来处理循环引用等特殊情况。

2、应用

2.1 二分查找 leetcode704

前提条件:有序数组、无重复元素

# 学前试写
class Solution:
    def search(self, nums: List[int], target: int) -> int:
        left=0
        right=len(nums)-1
        while left<=right:
            mid=int((left+right)/2) # int向下取整
            if nums[mid]==target:
                return mid
            if nums[mid]<target:
                left=mid+1
            if nums[mid]>target:
                right=mid-1
        return -1
  • 循环不变式
    二分法的两种写法,在于target取值区间定义,若定义于左闭右开,或左闭右闭,就要一直遵守。方法的选择决定了while条件语句中是否加入=号。
    上方代码认为是左闭右闭,所以加入等号,因为相等时取nums[left]=nums[right]=target时有意义。同时if (nums[mid] > target) right 要赋值为 mid - 1,因为当前这个nums[mid]一定不是target,那么接下来要查找的左区间结束下标位置就是 mid - 1。
    所以我们需要考虑清楚每一轮是在哪个区间取找,这点可以抽象成一个循环不变式。
    【详见算法导论】待更

2.2 移除元素 leetcode27

  • 暴力解法(两层for)
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        n=len(nums)
        i=0
        while i<n:
            if nums[i]==val:
                for j in range(i,n-1):
                    nums[j]=nums[j+1]
                n-=1
                i-=1
            i+=1
        return n

实际上还需要考虑数组内元素全等于val的特殊情形,此时i保持在0位置。

  • 双指针
    双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。
class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        n=len(nums)
        left=0
        right=0
        while right<n:
            if nums[right]!=val:
                nums[left]=nums[right]
                left+=1
            right+=1
        return left

本解中循环不变性是:区间 [0,left)中的元素都不等于 val。当左右指针遍历完输入数组以后,left 的值就是输出数组的长度。
即left始终指向下一个可以加入输出数组元素的存储位置,right是挨个确认是不是可以加入的元素。

2.3 有序数组的平方 leetcode977

  • 双指针 O(n)
class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        ans=[]
        left=0
        right=len(nums)-1
        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
        ans.reverse()
        return ans

如果不用reverse,可以先生成一个长度为n的列表,从后往前加入元素也行。
在Python中,列表的元素在内存中是存储在不同的位置上的,而不是挨着存储的。列表是一种动态数组,它会根据需要自动扩展或缩小内存空间。列表的元素可以是不同类型的对象,所以它们可能会占据不同大小的内存空间
在Python的列表中,使用下标索引一个元素的时间复杂度是O(1)。这是因为列表在内存中是通过连续的内存块存储的,并且列表对象包含对第一个元素的引用,以及每个元素的地址偏移量。

  • 暴力排序O(n+nlog(n))
class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        ans=[]
        n=len(nums)
        for i in range(n):
            ans.append(nums[i]**2)
        ans.sort()
        return ans

在Python中,sort方法的实现使用了一种叫做Timsort的排序算法。Timsort是一种混合排序算法,结合了合并排序(Merge Sort)和插入排序(Insertion Sort)的特性。

Timsort在排序过程中将列表分割成多个块,每个块称为run。然后,它使用插入排序将这些run进行排序,然后使用合并排序将排好序的run合并在一起,直到整个列表都被排序。

Timsort的时间复杂度为O(n log n),其中n是列表的大小。在最坏情况下,Timsort的时间复杂度为O(n log n),但在许多实际情况下,它可以达到O(n) 的性能,这是因为它利用了列表中已经有序或部分有序的特性。所以实际跑出来可能暴力要快一点。

2.4 长度最小的子数组 leetcode209

  • 滑动窗口 O(n)
class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        n=len(nums)
        ans=n+1
        sum=left=0
        for right,x in enumerate(nums):
            sum+=x
            while sum-nums[left]>=target:
                sum-=nums[left]
                left+=1
            if sum>=target:
                ans=min(ans,right-left+1)
        return ans if ans<n+1 else 0

enumerate介绍
在滑动窗口解法中,循环不变量(维持的窗口)是以right为右端点的满足条件的最小的子数组窗口(除了不存在情况)。能使用滑动窗口在于数组中存储的都是正整数。

  • 暴力遍历 O(n^2)
class Solution:
    def minSubArrayLen(self, target: int, nums: List[int]) -> int:
        n=len(nums)
        ans=n+1
        for i in range(n):
            k=0
            sum=0 
            for j in range(i,n):
                sum+=nums[j]
                k+=1
                if sum>=target:
                    ans=min(ans,k)
                    break
        if ans==n+1:
            ans=0
        return ans

2.5 螺旋矩阵Ⅱ leetcode59

  • 按圈层模拟矩阵生成过程:注意循环不变性(对于本题,每圈四个阶段,每个阶段就把那一行/一列填i-1个)
    时间复杂度O (n^2)
class Solution:
    def generateMatrix(self, n: int) -> List[List[int]]:
        nums=[[0]*n for i in range(n)]
        i=n # 后面i是处理边界的变化量,故不直接用n替代。
        num=1 # 记录填入的数值
        while i>1: #处理第i圈,此时该层边长为i
            k=(n-i)//2 # 求边长i时初始行数列数为多少
            for q in range(i-1):                      
                nums[k][k+q]=num
                num+=1
            for q in range(i-1):
                nums[k+q][k+i-1]=num
                num+=1
            for q in range(i-1):
                nums[k+i-1][k+i-1-q]=num
                num+=1
            for q in range(i-1):
                nums[k+i-1-q][k]=num
                num+=1
            i-=2
        if i==1:
            nums[(n-1)//2][(n-1)//2]=n**2
        return nums
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值