参考来源:代码随想录网站:代码随想录
前言
day1基本内容是二分查找(Leetcode 704)以及快慢指针删除元素(Leetcode 27)。在此基础上,Leetcode 34 和 35可以作为二分法的拓展内容,对二分法有一个更深的了解。
一、704 二分法查找元素
在数组中查找元素, 在数据量少的情况下可以使用暴力解法,即遍历数组,判断数组中的每个值是否与目标值相等, 时间复杂度为o(n)。但是在数据量大的时候,使用二分法可以提高查找效率,时间复杂度为o(log n),其中log以2为底。
举个例子,在1-100中随机生成一个数,我们去猜这个数是什么。我们首先要猜50,然后给出提示50比目标值大还是比目标值小,进行下一步猜测。若50大于目标值,则下一个猜测为25,若50小于目标值,则下一个猜测75.若等于目标值等于50则结束。以此类推,最终可以找到目标值,如果目标值存在于数组中。
二分法有两种写法,一种是左闭右闭,第二种是左闭右开。
1.左闭右闭
两种方法的区别主要在两个方面
①左指针l指向数组第一个元素,右指针r指向列表最后一个元素。所以当只剩1个元素需要判断的时候,l = = r, 此时仍需判断所剩的一个元素是否为目标值。因此while循环条件为l<=r.
②当目标值大于中间值时,下一次循环时,右指针应指向中间值的前一个值,所以r = middle - 1
def search(nums, target):
l = 0
r = len(nums) - 1
while l <= r:
middle = l + (r - l)//2
if nums[middle] < target:
l = middle + 1
elif nums[middle] > target:
r = middle -1
else:
return middle
return -1
2.左闭右开
①左闭右开时右指针r为数组最后一个元素的索引加一,因此当只剩一个元素的时候r=l+1, 所以while循环条件只需要l < r
②当目标值大于中间值时,下一次循环时,有效数组为中间值之前的数组, 所以中间值的上一个值为下一次循环的数组的最后一个值, 所以右指针应指向中间值,r = middle
def search(nums, target):
l = 0
r = len(nums)
while l < r:
middle = l+(r - l)//2
if nums[middle] < target:
l = middle + 1
elif nums[middle] > target:
r = middle
else:
return middle
return -1
二、27 删除元素
该题目给出了一个数组和要删除的目标值,本题目若采用暴力解法,则用两层for循环,一层遍历数组,一层更新数组。时间复杂度为o()。更好的方法是采用双指针法,快指针来遍历数组,慢指针更新数组。初始时,快慢指针都指向第一个元素,然后判断快指针所指的元素是否为要删除的目标值,若等于要删除的目标值target, 则快指针 + 1,若不等于target,则慢指针指向的元素赋值为快指针指向的元素。同时,快慢指针均 + 1。该算法的时间复杂度为o(n),要优于暴力解法。
代码如下
def delete(nums, val):
slow = 0
for fast in range(len(nums)):
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
nums = nums[:slow]
return slow, nums[:slow]
三、34 在排序数组中查找元素第一个元素和最后一个元素的位置
本题目为二分法的拓展题目,主要考察二分法的应用。解决本题先根据二分法定义一个Lower bound函数。该函数的参数为数组nums和目标值target。该函数的返回值则是num中,最后一个小于target的元素的下标再加1。例如:nums = [1, 2, 3, 4, 5], target = 3, 则最后一个小于3的元素是2,它的下标是1,所以返回值应该是2的下标再加一,也就是返回2。
lowerbound函数代码如下
def lowerbound(nums, target):
left = 0
right = len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid
return left
有了该函数后,如果要找的目标值为target。如果lowerbound(nums, target)返回的数组下标指向的元素不是target,或者返回值等于数组的长度,则target不在数组中。其他情况下,target存在nums中, 第一个target值在nums数组中的位置就是lowerbount(nums, target)的返回值,最后一个target的位置则是lowerbound(nums, target + 1)的返回值。
整体代码如下
def searchRange( nums, target):
def lowerbound(nums, target):
left = 0
right = len(nums)
while left < right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid
return left
start = lowerbound(nums, target)
end = lowerbound(nums, target + 1) - 1
if start == len(nums) or nums[start] != target:
return [-1, -1]
else:
return [start, end]
四、35 搜索插入位置
给定一个排序好的数组nums,将target插入nums,且插入后依然是按顺序排列。本题与34题几乎相同。还是用到之前的lowerbound函数。来判断数组中最后一个小于target值的位置,然后即可在最后一个小于target值的下一个位置插入target。
代码如下
def search(nums, target):
def insert(nums, target):
l = 0
r = len(nums) - 1
while l <= r:
middle = l + (r - l)//2
if nums[middle] < target:
l = middle + 1
else:
r = middle - 1
return l
last_ele = insert(nums, target)
if last_ele == len(nums) or nums[last_ele] != target:
nums.insert(target, last_ele)
return last_ele
总结
要熟练掌握二分法的两种写法,同时掌握在题目34、35中出现的lowerbound函数的写法以及具体意义。它是二分法的一种变种,原理与二分法基本类似。在题目27中,要掌握关于快慢指针的用法