数组理论基础及例题(一)

本文介绍了数组的基础理论,包括数组的内存布局和元素访问方式。接着详细讲解了二分查找的两种实现,分析了时间复杂度和空间复杂度。此外,还探讨了在数组中移除元素的不同方法及其效率。最后提到了有序数组平方的高效解决方案。
摘要由CSDN通过智能技术生成

数组理论基础

数组是存放在连续内存空间上的相同类型数据的集合。

数组可以方便地通过下标索引的方式获取到下标对应的数据。注意点:1. 数组下标都是从0开始的;2. 数组内存空间的地址是连续的。正是因为数组在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就要注意移动其他元素的地址。数组的元素是不能删的,只能覆盖。在C++中二维数组在地址空间上是连续的,Java不是。

二分查找

 力扣704

前提:有序数组、无重复元素(保证查找下标的唯一性)。一般有两种写法:

第一种:左闭右闭 [left, right]

  • left, right = 0, len(nums) - 1     右端点要数组长度-1,因为下标从0开始

  • while left <= right     闭区间,left == right 是有意义的

  • middle = left + (right - left) // 2     等同于(right + left) // 2,防止溢出所以用这种写法更好,Python中整除除法用 //,用 / 会导致浮点数

  • if nums[middle] > target: right = middle - 1     因为middle肯定不是target,右端点不会是middle,而应该是middle - 1
  • 时间复杂度:O(logn);空间复杂度:O(1)

第二种:左闭右开 [left, right)

  • left, right = 0, len(nums)     右端点是开区间,不包含,所以不用减一
  • while left < right     右端点开区间,left == right 没有意义

  • middle = left + (right - left) // 2     等同于(right + left) // 2,防止溢出所以用这种写法更好,Python中整除除法用 //,用 / 会导致浮点数

  • if nums[middle] > target: right = middle     target在左区间,右端点不包含,所以更新为middle

  • elif nums[middle] < target: left = middle + 1     target在右区间,但左端点是包含的,所以更新为middle + 1

  • 时间复杂度:O(logn);空间复杂度:O(1)

总结:区间的定义就是不变量,那么在循环中坚持根据查找区间的定义来做边界处理,就是循环不变量规则。二分法时一定要注意开闭区间的不同。 

移除元素

力扣27

前置知识:数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。调用函数删除元素时,返回的大小变小了,但实际上占用的物理空间是不变的,只是对最后一个元素不做处理而已,所以这是时间复杂度O(n)的操作,而不是O(1)的操作。

方法一:暴力解法,用while + for循环,while循环遍历数组元素,for循环更新数组:

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        i = 0
        size = len(nums)
        while i < size:
            if nums[i] == val:
                for j in range(i, size - 1):
                    nums[j] = nums[j + 1]
                i -= 1 #while循环里i会加1,但新的i位置上的元素还没有判断过
                size -= 1
            i += 1
        return size

时间复杂度:O(n^2);空间复杂度:O(1)。

方法二:双指针法(快慢指针法):通过快指针和慢指针在一个for循环下完成两个for循环的工作。首先定义快慢指针:

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向新数组下标的位置

主要想法是将快指针获取到的值赋给慢指针。下面的代码并没有改变元素的相对位置:

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        slow = 0
        for fast in range(len(nums)):
            if nums[fast] != val:
                nums[slow] = nums[fast]
                slow += 1
        return slow #slow指向的是新数组的后一个位置,没有新数组的元素,同时也是新数组长度的大小

时间复杂度:O(n);空间复杂度:O(1)。

方法三:相向双指针方法,基于元素顺序可以改变的题目描述,改变了元素的相对位置,确保了移动最少的元素:

class Solution:
    def removeElement(self, nums: List[int], val: int) -> int:
        left = 0
        right = len(nums) - 1
        while left <= right: #如果等于情况下指向的元素不是val,left还需要向后移一位
            #从左开始找等于val的元素
            while left <= right and nums[left] != val:
                left += 1
            #从右开始找不等于val的元素
            while left <= right and nums[right] == val:
                right -= 1
            if left < right:
                nums[left] = nums[right]
                left += 1
                right -= 1
        return left

时间复杂度:O(n);空间复杂度:O(1)。

有序数组的平方

力扣977

方法一:暴力排序,先平方后排序:

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        for i in range(len(nums)):
            nums[i] *= nums[i]
        nums.sort() #快速排序
        return nums

时间复杂度:O(nlogn)(O(n+nlogn) 取决于快排)。

方法二:双指针法。观察特点:平方后数组先非增后非降。所以用两个指针从两端向中间移动,新数组从最后一个开始向前填充。

class Solution:
    def sortedSquares(self, nums: List[int]) -> List[int]:
        n = len(nums)
        left, right, k = 0, n - 1, n - 1
        res = [-1] * n
        while left <= right: #最后一个元素是两个指针同时指向的
            ls = nums[left] ** 2
            rs = nums[right] ** 2
            if ls > rs:
                res[k] = ls
                left += 1
            else:
                res[k] = rs
                right -= 1
            k -= 1
        return res

时间复杂度:O(n)。

学习自【代码随想录】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值