数组理论基础
数组是存放在连续内存空间上的相同类型数据的集合。
数组可以方便地通过下标索引的方式获取到下标对应的数据。注意点: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)。
学习自【代码随想录】