704-二分查找
题目:给定一个 n
个元素有序的(升序)整型数组 nums
和一个目标值 target
,写一个函数搜索 nums
中的 target
,如果目标值存在返回下标,否则返回 -1。
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解题思路
- 该题直接用循环,时间复杂度O(n);二分法解答,时间复杂度O(logn)
- 使用二分的前提:该数组有序且无重复。
- 二分法边界问题,考虑循环不变量(区间的定义:通常左闭右闭或左闭右开),注意区间合理性
二分法1:左闭右闭区间
class Solution:
def search(self, nums: List[int], target: int) -> int:
# 1.左闭右闭[left,right]
left = 0;
# 右闭,序号需减一
right = len(nums)-1;
# 考虑合理性,left可以等于right
while (left <= right):
mid = left + (right - left) // 2;
if (target < nums[mid]):
# 需改右边界,序号必然小于mid,右闭区间包含了mid-1
right = mid-1;
elif (target > nums[mid]):
left = mid+1;
else:
return mid
return -1
二分法2:左闭右开区间
class Solution:
def search(self, nums: List[int], target: int) -> int:
# 1.左闭右开[left,right)
left = 0;
right = len(nums)-1;
# 考虑合理性,left不能等于right
while (left < right):
mid = left + (right - left) // 2;
if (target < nums[mid]):
# 需改右边界,序号必然小于mid,右开区间不包含了mid
right = mid;
elif (target > nums[mid]):
left = mid+1;
else:
return mid
return -1
难点
-
二分查找涉及的边界条件:① 循环条件:是
while(left < right)
还是while(left <= right);
① 边界修改:是right = middle
,还是right = middle - 1
要在二分查找的过程中,遵循循环不变量规则,想清楚对区间的定义,区间的定义就是不变量,每一次边界的处理都要坚持根据区间的定义来操作。
写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。
视频参考:b站代码随想录
-
mid的计算:
mid = (right + left) // 2
还是mid = left + (right - left) // 2
mid = (right + left) // 2
这种写法会发生上溢问题。int 占用 4 字节,32 比特,数据范围为:-2147483648 ~ 2147483647
[-2^31 ~ 2^31-1]
那么对于两个都接近
2147483647
的数字而言,它们相加的结果将会溢出,变成负数。所以,为了避免溢出情况的发生,我们不采用这种写法。
mid = left + (right - left) // 2
从左指针到右指针的一半距离 + 左指针的下标 才是符合题的下标位置。
总结
二分查找之前练过,但对于边界条件没仔细研究过,这次有了更清晰的了解。
27-移除元素
题目:给你一个数组 nums
和一个值 val
,你需要 原地 移除所有数值等于 val
的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。(元素的顺序可以改变;你不需要考虑数组中超出新长度后面的元素)
解题思路
- 数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。
- 该题直接用双重循环,时间复杂度O(n^2);双指针,时间复杂度O(n)
暴力法
class Solution:
def search(self, nums: List[int], target: int) -> int:
# 1.暴力
i=0;
length=len(nums);
# python中for循环里不能改指针!!
while i<length:
# 相等时后面元素全部前移
if val==nums[i]:
for j in range(i+1,length):
nums[j-1]=nums[j];
i-=1; # 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
length-=1; # 数组长度减一
i+=1;
return length
注意:python中for循环里不能改指针!!range遍历的底层实现是把当前的内容拷贝一份后进行遍历,遍历过程中的下标不会被改变 (否则如原对象变了,但拷贝的没变,下标改变会导致找不到对象报错)
双指针法(快慢指针法)
class Solution:
def search(self, nums: List[int], target: int) -> int:
# 2.快慢指针
fast = 0 # 快指针:新数组元素
slow = 0 # 慢指针:新数组更新的位置
size = len(nums)
while fast < size: # 不加等于是因为,a = size 时,nums[a] 会越界
# slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它赋给 slow 位置
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
- 快指针:寻找新数组的元素 ,即不等于目标值的元素
- 慢指针:指向更新 新数组下标的位置
总结
没想到,注意双指针法(快慢指针法)在数组和链表的操作中是非常常见的,下次可以尝试下这种思路。