LeetCode 704 二分查找
二分查找是数组的典型题目,一刷代码随想录以及后续力扣做题过程中经常遇到,时间复杂度为O(logn),空间复杂度O(1)。while循环的条件中,是否加等号取决于区间的特性,对于左闭右闭区间:
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:
return mid
elif nums[mid] > target:
right = mid - 1
else:
left = mid + 1
return -1
而Python数组区间默认为左闭右开,去掉等号的同时,right指针的写法也会略有不同:
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:
return mid
elif nums[mid] > target:
right = mid
else:
left = mid + 1
return -1
二分的原理掌握之后,对于Python用户来说,调用内置的bisect模块会更方便(特别是当二分只是解题的一小部分的时候)。
bisect文档链接:bisect — Array bisection algorithm — Python 3.11.2 documentation
‘This module provides support for maintaining a list in sorted order without having to sort the list after each insertion.’ 也就是说该模块用二分的方式维护一个有序数组,插入元素时不改变原数组有序的特性。
主要方法:bisect.bisect_left,bisect.bisect_right,bisect.bisect,bisect.insort_left, bisect.insort_right,bisect.insort,前三个均为查找某元素x在数组中的插入位置(下标),区别在于,x已在数组中时,left返回的是已有x最左下标,而right和默认的bisect返回最右下标;后三个为查找操作对应的插入操作,调用时会先执行对应的bisect找到插入位置,然后执行insert方法插入元素(Keep in mind that the O(logn)
search is dominated by the slow O(n) insertion step.)
LeetCode 34 在排序数组中查找元素的第一个和最后一个位置
题目链接:34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(Leetcode)
注释部分是之前的做法,手写二分查找,找到其中一个target值之后,分别向前向后找到target的边界,这次回顾题目并练习用内置bisect模块完成。
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
if not nums:
return [-1, -1]
# left, right = 0, len(nums) - 1
# while left <= right:
# mid = left + (right - left) // 2
# if target > nums[mid]:
# left = mid + 1
# elif target < nums[mid]:
# right = mid - 1
# else:
# # nums[mid] == target
# i, j = mid, mid
# while i >= 0 and nums[i] == target:
# i -= 1
# while j < len(nums) and nums[j] == target:
# j += 1
# return [i + 1, j - 1]
# return [-1, -1]
# bisect模块
left = bisect.bisect_left(nums, target)
right = bisect.bisect_right(nums, target)
return [left, right-1] if left != right else [-1, -1]
# 当left==right时说明target不在nums中,所以找到的插入位置是一样的
# 假设target在nums中只有一个且下标为i,left=i,right=i+1
LeetCode 27 移除元素
题目中明确要求需原地修改数组,暴力解法对每找到一个val,将其后所有元素整体往前移一位,需要两层for循环遍历,时间复杂度是O(n^2):
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
n = len(nums)
i = 0
while i < n:
if nums[i] == val:
for j in range(i+1, n):
nums[j-1] = nums[j]
n -= 1
else:
i += 1
# for i in range(n):
# if nums[i] == val:
# for j in range(i+1, n):
# nums[j-1] = nums[j]
# n -= 1
# i -= 1
return n
有些要注意的点:用原数组长度减一的方式记录数组长度变化,需在移动元素之后;用while循环更好控制指针i的变化(val之后的数组整体往前移之后不能执行i+=1的操作,还要比较移动之后i位置上的值),当然也可以像注释中的在for循环nums[i] == val时让i -= 1,下一轮判断对象仍旧是nums[i]。
之前刷过题,所以知道双指针解法,它的奇妙之处在于,当前遍历的nums[i]不等于val时才进行覆盖和left指针后移操作,等于val时不做任何操作,保证left直接记录移除val之后的数组长度,时间复杂度优化到O(n)。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
left, n = 0, len(nums)
for i in range(n):
if nums[i] == val:
continue
else:
nums[left] = nums[i]
left += 1
return left
今天任务量不大,复习基本算法,主要都花时间在博客记录上了。