数组基础:
- 数组是存放在连续内存空间上的相同类型数据的集合。
- 数组可以方便的通过下标索引的方式获取到下标下对应的数据。
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的。
正是因为数组的在内存空间的地址是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址。数组的元素是不能删的,只能覆盖。
704. 二分查找
熟悉根据左闭右开,左闭右闭两种区间规则写出来的二分法。一定要注意边界条件 。
关键词:有序且升序,无重复元素。因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件。
二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 while(left < right)
还是 while(left <= right)
,到底是right = middle
呢,还是要right = middle - 1
呢?
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
-
左闭右闭:[left, right]
因为定义target在[left, right]区间,所以有两点:
1. while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
2. if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
3. left,right=0,len(nums)-1# 定义target在左闭右闭的区间里,[left, right]
时间复杂度:O(log n);空间复杂度:O(1)
def search(self, nums: List[int], target: int) -> int:
#左闭右闭
left,right=0,len(nums)-1# 定义target在左闭右闭的区间里,[left, right]
while left<=right:
middle=(left+right)//2
if nums[middle]<target:
left=middle+1
elif nums[middle]>target:
right=middle-1
else:
return middle
return -1
-
左闭右开:[left, right)
有如下两点:
1. while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
2. if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
3. left,right=0,len(nums)# 定义target在左闭右开的区间里,即:[left, right)
- 时间复杂度:O(log n);空间复杂度:O(1)
left,right=0,len(nums)# 定义target在左闭右开的区间里,即:[left, right)
while left<right:
middle=(left+right)//2
if nums[middle]<target:
left=middle+1
elif nums[middle]>target:
right=middle
else:
return middle
return -1
27. 移除元素
题目:给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素。元素的顺序可能发生改变。然后返回 nums
中与 val
不同的元素的数量。
解决方法:快慢指针。在数组和链表的操作中非常常见,很多题目,链表、数字等操作,都使用双指针法。
原地移除,快指针找不等于val的元素,慢指针将快指针找到的元素覆盖原来的元素。
def removeElement(self, nums: List[int], val: int) -> int:
# 快慢指针
slow,fast=0,0
while fast<len(nums):
if nums[fast]!=val:
nums[slow]=nums[fast]# 覆盖即移除
slow+=1
fast+=1
else:
fast+=1
return slow
35.搜索插入位置
题目:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n)
的算法。
这题还多了个额外的条件,即如果不存在数组中的时候需要返回按顺序插入的位置,考虑这个插入的位置 pos,它成立的条件为:nums[pos−1]<target≤nums[pos]。
其中 nums代表排序数组。由于如果存在这个目标值,返回的索引也是 pos,因此我们可以将两个条件合并得出最后的目标:「在一个有序数组中找第一个大于等于 target的下标」。不断用二分法逼近查找第一个大于等于 target 的下标 。
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
#若存在,则返回下标,不存在,则返回比他稍大元素的下标,也就是返回右坐标
# nums[pos-1]<target<=nums[pos],返回pos的下标
# 找到大于等于的第一个数
# 左闭右开
left,right=0,len(nums)
while left<right:
middle=(left+right)//2
if nums[middle]<target:
left=middle+1
else:
right=middle
return right
34. 在排序数组中查找元素的第一个和最后一个位置
题目:给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target
,返回 [-1, -1]
。你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
def lower_bound1(nums,target):
#二分法,左闭右开
left,right=0,len(nums)#[left,right)
while left<right:#相等时没有意义
middle=(left+right)//2
if nums[middle]<target:
left=middle+1#[midlle+1,right)
else:
right=middle#[left,middle)
return left# # 返回 left 还是 right 都行,因为循环结束后 left == right
# def lower_bound2(nums,target):
# #二分法,左闭右闭
# left,right=0,len(nums)-1#[left,right]
# while left<=right:
# middle=(left+right)//2
# if nums[middle]<target:
# left=middle+1
# else:
# right=middle-1
# return left
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
# start是大于等于,end是小于等于target的位置
start=lower_bound1(nums,target)#找到第一个等于target的位置,左边小于target
if start==len(nums) or nums[start]!=target:
return [-1,-1] # nums 中没有 target
#如果start存在,那么end一定存在
end=lower_bound1(nums,target+1)-1
return [start,end]