关于数组的算法

数组是什么?

在空间中,一个连续内存地址,并且元素类型相同的数据结构

数组的操作与时间复杂度

  1. 查询:O(1)
    因为数组的内存空间是连续的,并且,数组的下标是0开始计算累加的,所以每当我们去通过下标查询数组元素的时候,被查询的位置是可以被计算出来的。比如当前有个数组,里面有四个元素,分别存储了1,2,3,4。这时候,第一个元素的内存地址是100,每个元素占4字节内存空间,所以数组中的第三个元素内存地址是100+4*2=108,这时候已经算出内存地址了,就可以直接获取到对应的元素了
  2. 插入:O(N)
    插入之所以是O(N)的世界复杂的,是因为每次插入,除去在数组有空位,并且末尾插入的时候,其他时候均需要把数组中的元素向后面迁移,为新的数据腾出空间
  3. 修改:O(N)
    修改的元素我们是不确定的,除非我们通过下标指定某个元素被修改了。比如我们需要修改元素值为4的元素,这时候我们需要去遍历数组中的每一个元素,判断值是否为4,然后进行修改
  4. 删除:O(N)
    删除数组和修改同理,指定下标删除是O(1),但是要对某个值进行删除,则需要遍历数组

数组练习题

  1. 485最大连续1的个数
# 最开始想到的方法
class Solution(object):
    def findMaxConsecutiveOnes(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n = 0
        x = 0
        for i in nums:
            if i == 1:
                n += 1
            else:
                if n > x:
                    x = n
                n = 0
        if n > x:
            return n
        else:
            return x

# 优化了一下的方法
class Solution(object):
    def findMaxConsecutiveOnes(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        maxCount = count = 0

        for i, num in enumerate(nums):
            if num == 1:
                count += 1
            else:
                maxCount = max(maxCount, count)
                count = 0
        
        maxCount = max(maxCount, count)
        return maxCount

# 两个方法其实是一样的,只不过优化后的,使用了内置函数max对两个数字进行大小比较
  1. 283移动零
    在力扣上的解题方案是使用双指针对元素进行判断和换位
def moveZeroes(nums: List[int]) -> None:
    n = len(nums)
    left = right = 0
    while right < n:
        if nums[right] != 0:
            nums[left], nums[right] = nums[right], nums[left]
            left += 1
        right += 1

其实也可以通过空间去换,但是这里的nums指向地址其实已经发生改变了,如果是在函数外面的nums传入进来,并且打印的也是外面的nums,那么我们这个解法是错误的。毕竟外面的nums是完全没改变的。

def moveZeroes(self, nums):
    arr_1 = []
    arr_2 = []
    for i, v in enumerate(nums):
        if v == 0:
            arr_1.append(v)
        else:
            arr_2.append(v)
    nums = arr_2 + arr_1

力扣里面的题目说明是这个的:

  1. 27移动元素
    我这里搞了三个解法,但是时间复杂度,呃呃呃呃,有些不理想

方法一:

会有个额外的数组被创建,用作循环。然后通过从末尾删除,就不会影响nums的下标值了。当然,题目中说的不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。我选择忽略,欸嘿嘿。顺带说一下,切片是复制整个列表,到一个新的空间中,所以时间复杂度也应该是O(N)的
这个方案的时间复杂度是O(2N),空间复杂度是O(2N)

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        arr = nums[::-1]
        last = len(nums) - 1
        for i, v in enumerate(arr):
            if v == val:
                n = last - i
                nums.pop(n)
        return len(nums)

方法二

这个方法比较粗暴,只管查nums中是否存在val,如果有,则用remove直接删除。顺带解释一下python的作用:该方法没有返回值但是会移除列表中的某个值的第一个匹配项。这里说明remove是遍历查询,而in也是遍历查询
所以时间复杂度:O(N^2),空间复杂度为O(N)

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        while val in nums:
            nums.remove(val)
        return len(nums)

方法三

方法三的话是通过index返回下标去做对应的删除动作的,而index时间复杂度是O(N),并且,在index找不到值的时候是会报错的,所以需要try一下
所以这里的总体时间复杂度是O(N^2),空间复杂度是O(N)

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        try:
            while True:
                n = nums.index(val)
                nums.pop(n)
        except:
            return nums

官方题解

这个是通过双指针做的,时间复杂度为O(N),空间复杂度为O(N)。因为题目中有说明


所以这样写也可以算是正确的,返回的left值最终指向的就是满足条件的值

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        left = 0
        for v in nums:
            if v != val:
                nums[left] = v
                left += 1
        return left

或者这样写更好理解吧

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        left = 0
        for i, v in enumerate(nums):
            if v != val:
                nums[left], nums[i] = v, nums[left]
                left += 1
        return left
posted @ 2022-08-03 17:06  影梦无痕  阅读(39)  评论(0)    收藏  举报