数组理论基础
数组是存放在连续内存空间上的相同类型数据的集合。
数组可以方便的通过下标索引的方式获取到下标对应的数据。
举一个字符数组的例子,如图所示:
需要两点注意的是
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的
正是因为数组在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。
例如删除下标为3的元素,需要对下标为3的元素后面的所有元素都要做移动操作,如图所示:
数组的元素是不能删的,只能覆盖。
二分法
二分法的前提是数组为有序数组,且无重复数组
易错点:
#易错点1:
while left<right || while left<=right
#易错点2:
if nums[mid] > target:
right =mid || right = mid -1
左闭右开,左闭右闭 两种区间规则
704. 二分查找
给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1
。
提示:
- 你可以假设
nums
中的所有元素是不重复的。 n
将在[1, 10000]
之间。nums
的每个元素都将在[-9999, 9999]
之间。
方法一:左闭右闭
[left,right]:
while left <=right.
# left和while可以相等,例如:[1,1]是合法区间
if nums[mid] > target:
right = mid -1
# mid值大于target,下一个区间不能包含mid值
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums)-1
while left <= right:
mid = (left+right)//2
if nums[mid] < target:
left = mid +1 #[mid+1, right]
elif nums[mid]> target:
right = mid-1 #[left, mid-1]
else:
return mid
return -1
# 如果target一定存在也可以写成:
# lower_bound 返回最小的满足 nums[i] >= target 的 i
# 如果数组为空,或者所有数都 < target,则返回 len(nums)
# 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]
def lower_bound(nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1 # 闭区间 [left, right]
while left <= right: # 区间不为空
# 循环不变量:
# nums[left-1] < target
# nums[right+1] >= target
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1 # 范围缩小到 [mid+1, right]
else:
right = mid - 1 # 范围缩小到 [left, mid-1]
return left # 或者 right+1
遇到的问题:
- mid = (left+right) //2一定要放进循环,确保每次迭代都更新mid
- 第二段代码
- 如果
target
存在于nums
中,left
将最终指向target
的索引。(可以直接使用left
作为插入位置)- 如果
target
不在nums
中,left
将指向第一个大于target
的元素的索引,或者是数组的末尾。
方法二:左闭右开
[left,right)
- 除2个易错点外还要注意right的取值
right = len(nums) #右开,取不到len(nums)
while left < right:
#[1,1)无意义
if nums[mid] < target:
left = mid+1 # [mid+1,right) 左闭不能包含mid
elif nums[mid] > target:
right = mid #[left,mid)
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) #[left,right)
while left < right:
mid = (left+right)//2
if nums[mid] < target:
left = mid+1 #[mid+1, right]
elif nums[mid] > target:
right = mid #[left,mid)区间不可包含mid,右开取不到
else:
return mid
return -1
- 时间复杂度:O(log n)
- 空间复杂度:O(1)
27. 移除元素
给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素。元素的顺序可能发生改变。然后返回 nums
中与 val
不同的元素的数量。
假设 nums
中不等于 val
的元素数量为 k
,要通过此题,您需要执行以下操作:
- 更改
nums
数组,使nums
的前k
个元素包含不等于val
的元素。nums
的其余元素和nums
的大小并不重要。 - 返回
k
。
数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。
库函数:
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
m = nums.count(val)
for i in range(m):
nums.remove(val)
双指针思想:
把 nums 中的不等于 val 的数,依次填在 0,1,2,⋯ 这些下标上。快指针去搜索不等于val的值,慢指针记录下标。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
k = 0 #初始化要填入的下标k=0, 慢指针
for x in nums: #快指针
if x!=val:
nums[k] = x
k+=1
return k
算法复杂度:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
总结:
- 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
- 慢指针:指向更新 新数组下标的位置
977. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
暴力法:
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
for i in range(len(nums)):
nums[i] *= nums[i] #平方后排序
nums.sort() #sort函数的时间复杂度为O(N logN)
return nums
#暴力排序法+列表推导法
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
return sorted(x*x for x in nums)
算法复杂度
- O(nlogn)
- O(1)
双指针思想:
数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。
此时可以考虑双指针法了,i指向起始位置,j指向终止位置。
定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。
与其从中间开始向两边合并,不如从两边开始向中间合并,这样无需计算从中间的哪个位置开始。
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
l = 0
r = len(nums)-1
res = [0]*(len(nums)) #定义一个新数组
for i in range(len(nums)-1,-1,-1):
x = nums[l]**2
y = nums[r]**2
if x >y:
res[i] = x
l+=1
else:
res[i] = y
r-=1
return res
算法复杂度:O(n)