《代码随想录》学习笔记,原链接:https://programmercarl.com/
704.二分查找
(1)二分查找法前提
- 数组为有序数组
- 数组中无重复元素(不然可能返回的下标不唯一)
(2)循环不变量(区间定义)
区间的定义就是不变量,循环中坚持根据区间的定义来做边界处理,就是循环不变量规则。
一般的区间定义分为两种:
- 左闭右闭 [left, right],即能取到left和right两个端点:
left, right = 0, len(nums) - 1 # 定义左闭右闭的区间端点,[left, right]
while left <= right:
middle = left + (right - left) // 2
if target < nums[middle]:
right = middle - 1 # target在区间的左半部分,即在[left, middle - 1]范围内
elif target > nums[middle]:
left = middle + 1 # target在区间的右半部分,即在[middle + 1, right]范围内
- 左闭右开 [left, right),即只能取到左端点left而不能取到右端点right:
left, right = 0, len(nums) - 1 # 定义左闭右闭的区间端点,[left, right]
while left < right:
middle = left + (right - left) // 2
if target < nums[middle]:
right = middle # target在区间的左半部分,即在[left, middle]范围内
elif target > nums[middle]:
left = middle + 1 # target在区间的右半部分,即在[middle + 1, right]范围内
(3)二分查找法代码
时间复杂度 O(log n),空间复杂度O(1)。
- 左闭右闭:
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1 # 定义左闭右闭的区间端点,[left, right]
while left <= right:
middle = left + (right - left) // 2
if nums[middle] > target:
right = middle - 1 # target在区间的左半部分,即在[left, middle - 1]范围内
elif nums[middle] < target:
left = middle + 1 # target在区间的右半部分,即在[middle + 1, right]范围内
else:
return middle # 数组中找到target值,直接返回对应的下标
return -1 # 未找到target值
- 左闭右开:
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) # 定义左闭右开的区间端点,[left, right)
while left < right: # 因为不能取到右端点right,所以left == right的时候,在[left, right)里表示无效的空间,所以使用 <
middle = left + (right - left) // 2
if nums[middle] > target:
right = middle # target在区间的左半部分,即在[left, middle)范围内
elif nums[middle] < target:
left = middle + 1 # target在区间的左半部分,即在[middle + 1, right)范围内
else:
return middle # 数组中找到target值,直接返回对应的下标
return -1 # 未找到target值
【注】“search(self, nums: List[int], target: int) -> int:”中的List[int]和int表示类型提示。但是Python 程序在运行时不强制执行函数和变量的类型提示,也就是说写错了也不会影响程序运行,但是不建议这么做。
(4)ACM模式
class Solution:
def search(self, nums, target):
left, right = 0, len(nums) - 1 # 定义左闭右闭的区间端点,[left, right]
while left <= right:
middle = left + (right - left) // 2
if nums[middle] > target:
right = middle - 1 # target在区间的左半部分,即在[left, middle - 1]范围内
elif nums[middle] < target:
left = middle + 1 # target在区间的右半部分,即在[middle + 1, right]范围内
else:
return middle # 数组中找到target值,直接返回对应的下标
return -1 # 未找到target值
# 自定义数据列表和target值
nums = list(map(int, input("输入已排序的整数数组:").split(",")))
target = int(input("输入target值:"))
# 二分查找
binarySearch = Solution()
result = binarySearch.search(nums, target)
print(result)
【注1】list(map(int, input("输入已排序的整数数组:").split(","))),分析:
- input() 函数:用于从用户输入获取一个由逗号分隔的整数组成的字符串。
- split() 方法:用于将输入的字符串按照逗号分隔成多个子字符串,并返回子字符串构成的列表。如输入1,2,3时,将返回['1', '2', '3']。
- map() 函数:用于对子字符串列表中的每个元素应用指定的函数。具体来说,map() 函数接收两个参数,第一个参数是一个函数,第二个参数是一个可迭代对象,它将该函数依次作用于可迭代对象中的每个元素,并返回一个由结果组成的新的可迭代对象。例如map(int, ['1', '2', '3']) ,会将字符串列表中的每个元素转换为整数类型,并返回一个由整数组成的新的可迭代对象 [1][2][3]。
- int() 函数:用于将字符串转换为整数类型的函数。
- list() 函数:用于将可迭代对象 [1][2][3]转换为列表类型[1, 2, 3],从而得到用户输入的整数列表。
【注2】可迭代对象:
简单来说,任何可以循环遍历的对象都是可迭代对象,如字符串、列表、元组、集合、字典等等之类的对象都属于可迭代对象。
- 使用isinstance()函数,可以判断对象是否为可迭代对象
- 使用
dir()函数
查看对象内所有的属性与方法,如dir({"name": "ayaka"})。之后发现,可迭代对象都构建了 ``__iter__`` 方法,而不可迭代对象没有构建,因此也可通过此特点来判断某一对象是不是可迭代对象。
二分查找相关题目
(1)35.搜索插入位置
首先,这道题目的前提是数组是有序数组,是使用二分查找的基础条件。
同时题目还强调数组中无重复元素,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的。
- 核心代码模式
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1 # 定义左闭右闭的区间端点,[left, right]
while left <= right:
middle = left + (right - left) // 2
if target < nums[middle]:
right = middle - 1 # target在区间的左半部分,即在[left, middle - 1]范围内
elif target > nums[middle]:
left = middle + 1 # target在区间的左半部分,即在[middle + 1, right)范围内
elif target == nums[middle]:
return middle # 返回目标值在数组中的索引
return left # 返回被插入的位置索引
【注】关于当目标值不存在与数组中时,最后返回的插入位置return left的推导分析:
- ACM模式
class Solution:
def searchInsert(self, nums, target):
left, right = 0, len(nums) - 1 # 定义左闭右闭的区间端点,[left, right]
while left <= right:
middle = left + (right - left) // 2
if target < nums[middle]:
right = middle - 1 # target在区间的左半部分,即在[left, middle - 1]范围内
elif target > nums[middle]:
left = middle + 1 # target在区间的左半部分,即在[middle + 1, right)范围内
elif target == nums[middle]:
return middle
return left
# 自定义数据列表和target值
nums = list(map(int, input("输入已排序的整数数组:").split(",")))
target = int(input("输入target值:"))
# 搜索插入位置
Solution = Solution()
result = Solution.searchInsert(nums, target)
print(result)
(2)34. 在排序数组中查找元素的第一个和最后一个位置
- 核心代码模式
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
left, right = 0, len(nums) - 1 # 定义左闭右闭的区间端点,[left, right]
while left <= right:
middle = left + (right - left) // 2
if target < nums[middle]: # target在区间的左半部分,即在[left, middle - 1]范围内
right = middle - 1
elif target > nums[middle]: # target在区间的左半部分,即在[middle + 1, right)范围内
left = middle + 1
elif target == nums[middle]:
mid_left, mid_right = middle, middle
while mid_left >= left and nums[mid_left] == target: # 寻找第一个位置
mid_left -= 1
while mid_right <= right and nums[mid_right] == target: # 寻找第二个位置
mid_right += 1
return [mid_left + 1, mid_right - 1] # 返回元素的第一个位置和最后一个位置
return [-1, -1] # 未找到元素
【注】返回元素的第一个位置和最后一个位置分析:
- ACM模式
class Solution:
def searchRange(self, nums, target):
left, right = 0, len(nums) - 1 # 定义左闭右闭的区间端点,[left, right]
while left <= right:
middle = left + (right - left) // 2
if target < nums[middle]: # target在区间的左半部分,即在[left, middle - 1]范围内
right = middle - 1
elif target > nums[middle]: # target在区间的左半部分,即在[middle + 1, right)范围内
left = middle + 1
elif target == nums[middle]:
mid_left, mid_right = middle, middle
while mid_left >= left and nums[mid_left] == target: # 寻找第一个位置
mid_left -= 1
while mid_right <= right and nums[mid_right] == target: # 寻找第二个位置
mid_right += 1
return [mid_left + 1, mid_right - 1] # 返回元素的第一个位置和最后一个位置
return [-1, -1] # 未找到元素
# 自定义数据列表和target值
nums = list(map(int, input("输入已排序的整数数组:").split(",")))
target = int(input("输入target值:"))
# 在排序数组中查找元素的第一个和最后一个位置
Solution = Solution()
result = Solution.searchRange(nums, target)
print(result)
(3)69.x的平方根
- 核心代码模式
class Solution:
def mySqrt(self, x: int) -> int:
left, right = 0, x # 定义左闭右闭的区间端点,[left, right]
while left <= right:
middle = left + (right - left) // 2
# middle * middle 花费的时间远小于middle ** 2
if middle * middle > x: # 平方根在区间的左半部分,即在[left, middle - 1]范围内
right = middle - 1
elif middle * middle < x: # 平方根在区间的右半部分,即在[middle + 1, right]范围内
left = middle + 1
elif middle * middle == x:
return middle # 返回x的平方根
return right # 返回x平方根的整数部分
【注】返回x平方根的整数部分为right的分析:
- ACM模式
class Solution:
def mySqrt(self, x):
left, right = 0, x # 定义左闭右闭的区间端点,[left, right]
while left <= right:
middle = left + (right - left) // 2
# middle * middle 花费的时间远小于middle ** 2
if middle * middle > x: # 平方根在区间的左半部分,即在[left, middle - 1]范围内
right = middle - 1
elif middle * middle < x: # 平方根在区间的右半部分,即在[middle + 1, right]范围内
left = middle + 1
elif middle * middle == x:
return middle # 返回x的平方根
return right # 返回x平方根的整数部分
# 自定义数据x值
x = int(input("输入正整数值x:"))
# 在排序数组中查找元素的第一个和最后一个位置
Solution = Solution()
result = Solution.mySqrt(x)
print(result)
(4)367.有效的完全平方数
- 核心代码模式
class Solution:
def isPerfectSquare(self, num: int) -> bool:
left, right = 0, num # 定义左闭右闭的区间端点,[left, right]
while left <= right:
middle = left + (right - left) // 2
if middle * middle > num: # 平方根在区间的左半部分,即在[left, middle - 1]范围内
right = middle - 1
elif middle * middle < num: # 平方根在区间的右半部分,即在[middle + 1, right]范围内
left = middle + 1
elif middle * middle == num:
return True
return False # 不是完全平方数
- ACM模式
class Solution:
def isPerfectSquare(self, num):
left, right = 0, num # 定义左闭右闭的区间端点,[left, right]
while left <= right:
middle = left + (right - left) // 2
if middle * middle > num: # 平方根在区间的左半部分,即在[left, middle - 1]范围内
right = middle - 1
elif middle * middle < num: # 平方根在区间的右半部分,即在[middle + 1, right]范围内
left = middle + 1
elif middle * middle == num:
return True
return False # 不是完全平方数
# 自定义数据num值
num = int(input("输入正整数值num:"))
# 有效的完全平方数
Solution = Solution()
result = Solution.isPerfectSquare(num)
print(result)