1.给定长度为n的数组,元素从小到大排列,不包含重复元素。查找并返回target的索引,不包含该元素返回-1.
第一次尝试:
def binary_search(nums, target):
"""二分查找"""
# 双闭区间,左右操作对称,创建左指针和右指针,分别指向数组的首元素和尾元素
i = 0
j = len(nums) - 1
# 计算中间元素,向下取整数
m = (i + j) // 2
# 当j小于i时,查找结束
while i <= j:
# 若中间元素小于目标元素,则目标元素在中间元素后,即区间[m + 1, j]
if nums[m] < target:
i = m + 1
# 若中间元素大于目标元素,则目标元素在中间元素前,即区间[i, m - 1]
elif nums[m] > target:
j = m - 1
# 若中间元素等于目标元素,则找到目标元素,返回中间元素的索引
else:
return m
# 若没找到元素,返回-1
return -1
if __name__ == '__main__':
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(binary_search(nums, 2))
错误:
1.循环的过程中没有计算中间元素
第二次尝试:
def binary_search(nums, target):
"""二分查找"""
# 双闭区间,左右操作对称,创建左指针和右指针,分别指向数组的首元素和尾元素
i = 0
j = len(nums) - 1
# 当j小于i时,查找结束
while i <= j:
# 计算中间元素,向下取整数
m = (i + j) // 2
# 若中间元素小于目标元素,则目标元素在中间元素后,即区间[m + 1, j]
if nums[m] < target:
i = m + 1
# 若中间元素大于目标元素,则目标元素在中间元素前,即区间[i, m - 1]
elif nums[m] > target:
j = m - 1
# 若中间元素等于目标元素,则找到目标元素,返回中间元素的索引
else:
return m
# 若没找到元素,返回-1
return -1
if __name__ == '__main__':
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(binary_search(nums, 2))
补充:
1.计算中间元素下标时,若i,j数过大可能超过int类型范围,此时可使用公式m = i + (j - i) // 2
2.长度为n的有序数组,不存在重复元素。将target插入到数组中,并保持其有序性。若数组中存在target,则插入到左方。返回插入后target在数组中的索引。
第一次尝试:
def binary_search_insertion(nums, target):
"""
二分查找插入点
1.当target存在时,数组中的target的索引就是插入位置,双闭区间
2.当target不存在时,循环后,i会指向首个大于target的元素,j指向首个小于target的元素
"""
# 创建左指针和右指针,分别指向列表首元素和尾元素
i, j = 0, len(nums) - 1
# 循环,直到i > j,即左指针大于右指针
while i <= j:
# 计算中间元素的索引
m = (i + j) // 2
# 如果中间元素小于target,则插入位置在区间[m + 1, j]中
if nums[m] < target:
i = m + 1
# 如果中间元素大于target,则插入位置在区间[i, m - 1]中
elif nums[m] > target:
j = m - 1
# 循环后,返回插入位置i
return i
if __name__ == '__main__':
nums = [1, 3, 5, 6, 7, 8, 9, 10]
print(binary_search_insertion(nums, 4))
错误:
1.返回值没有满足题目要求,仅考虑到数组中没有目标元素target的情况
第二次尝试:
def binary_search_insertion(nums, target):
"""
二分查找插入点
1.当target存在时,数组中的target的索引就是插入位置
2.当target不存在时,循环后,i会指向首个大于target的元素,j指向首个小于target的元素
"""
# 创建左指针和右指针,分别指向列表首元素和尾元素,双闭区间
i, j = 0, len(nums) - 1
# 循环,直到i > j,即左指针大于右指针
while i <= j:
# 计算中间元素的索引
m = (i + j) // 2
# 如果中间元素小于target,则插入位置在区间[m + 1, j]中
if nums[m] < target:
i = m + 1
# 如果中间元素大于target,则插入位置在区间[i, m - 1]中
elif nums[m] > target:
j = m - 1
# 如果列表中有目标元素,则返回目标元素的索引
else:
return m
# 循环后,返回插入位置i
return i
if __name__ == '__main__':
nums = [1, 3, 5, 6, 7, 8, 9, 10]
print(binary_search_insertion(nums, 5))
3.长度为n的有序数组,存在重复元素。将target插入到数组中,并保持其有序性。若数组中存在重复target,则插入到最左方。返回插入后target在数组中的索引。
第一次尝试:
def binary_search_insertion(nums, target):
# 创建左指针和右指针,闭区间
i, j = 0, len(nums) - 1
# 循环,直到i > j
while i <= j:
# 计算中间元素索引
m = (i + j) // 2
# 如果中间元素小于目标元素,说明目标元素在区间[m + 1, j]中
if nums[m] < target:
i = m + 1
# 如果中间元素大于目标元素,说明目标元素在区间[i, m - 1]中
elif nums[m] > target:
j = m - 1
# 查找到目标元素,但不知道左边和右边分别有多少元素
# 但是可以知道,左边的元素小于等于目标元素,右边的元素大于等于目标元素
# 所以,可以知道若左边还有等于目标元素的元素,应位于区间[i, m - 1]中
else:
j = m - 1
# 循环后,i指向最左边的目标元素,j指向首个小于目标元素的元素
return i
if __name__ == '__main__':
nums = [1, 2, 3, 3, 3, 6, 7, 8, 9]
print(binary_search_insertion(nums, 3))
4.长度为n的有序数组nums,可能包含重复元素。返回数组中最左target元素的索引。若不包含此元素,返回-1。即查找左边界
第一次尝试:
def binary_search_left_edge(nums, target):
# 创建左指针和右指针,左指针指向数组首元素,右指针指向数组尾元素
i, j = 0, len(nums) - 1
# 循环,直到左指针大于右指针
while i <= j:
# 计算中间元素的索引
m = (i + j) // 2
# 如果中间元素小于目标元素,说明目标元素在中间元素的右边,即区间[m + 1, j]
if nums[m] < target:
i = m + 1
# 如果中间元素大于目标元素,说明目标元素在中间元素的左边,即区间[i, m - 1]
elif nums[m] > target:
j = m - 1
# 如果中间元素等于目标元素,缩小右边界,寻找左边的目标元素
# 循环后i指向最左边的目标元素,j指向首个小于目标元素的元素
else:
j = m - 1
# 找不到目标元素时会出现两种情况
# 1.目标元素大于列表尾元素,循环后i = len(nums)
# 2.目标元素小于列表首元素,循环后i = 0,即nums[i] 不等于target
if i == len(nums) or nums[i] != target:
return -1
return i
if __name__ == '__main__':
nums = [1, 2, 3, 3, 3, 3, 7, 8, 9]
print(binary_search_left_edge(nums, 0))
5.长度为n的数组nums,可能含重复元素。查找数组中最右的目标元素target的索引,若数组中不存在则返回-1。即查找右边界
第一次尝试:与查找左边界类似,更改nums[m] == target的条件即可,改为缩小左边界
def binary_search_right_edge(nums, target):
# 创建左指针和右指针,指向列表中首元素和尾元素,闭区间
i, j = 0, len(nums) - 1
# 循环,直到右指针大于左指针
while i <= j:
# 计算中间元素索引
m = (i + j) // 2
# 如果中间元素小于目标元素,说明目标元素在中间元素的右边,即在区间[m + 1, j]中
if nums[m] < target:
i = m + 1
# 如果中间元素大于目标元素,说明目标元素在中间元素的左边,即在区间[i, m - 1]
elif nums[m] > target:
j = m - 1
# 如果中间元素等于目标元素,缩小左边界,
else:
i = m + 1
# 当找不到目标元素时,会出现两种情况
# 1.目标元素大于数组中尾元素,循环后i指向len(nums)
# 2.目标元素小于数组中首元素,循环后i指向nums[0],即nums[i] != target
if i == len(nums) or nums[i] != target:
return -1
return i
if __name__ == '__main__':
nums = [1, 2, 3, 3, 3, 3]
print(binary_search_right_edge(nums, 3))
错误:
1.返回索引错误,i指向首个大于目标元素target的索引
第二次尝试:
在搜索元素插入位置时
1.目标元素在列表中,此时i指向目标元素,j指向首个小于目标元素的元素
2.目标元素不在列表中,此时i指向首个大于目标元素target的元素,j指向首个小于目标元素target的元素
在搜索边界时,更改了中间元素等于目标元素时执行的步骤,由返回插入索引变为缩小边界范围
1.求左边界,缩小右边界范围,直到左指针大于右指针,i指向左边界,j指向首个小于目标元素的元素
2.求右边界,缩小左边界范围,直到左指针大于右指针,j指向右边界,i指向首个大于目标元素的元素
def binary_search_right_edge(nums, target):
# 创建左指针和右指针,指向列表中首元素和尾元素,闭区间
i, j = 0, len(nums) - 1
# 循环,直到右指针大于左指针
while i <= j:
# 计算中间元素索引
m = (i + j) // 2
# 如果中间元素小于目标元素,说明目标元素在中间元素的右边,即在区间[m + 1, j]中
if nums[m] < target:
i = m + 1
# 如果中间元素大于目标元素,说明目标元素在中间元素的左边,即在区间[i, m - 1]
elif nums[m] > target:
j = m - 1
# 如果中间元素等于目标元素,缩小左边界,
else:
i = m + 1
# 当找不到目标元素时,会出现两种情况
# 1.目标元素大于数组中尾元素,循环后i指向len(nums)
# 2.目标元素小于数组中首元素,循环后i指向nums[0],即nums[i] != target
if j == len(nums) or nums[j] != target:
return -1
return j
if __name__ == '__main__':
nums = [1, 2, 3, 3, 3, 3]
print(binary_search_right_edge(nums, 3))
6.复用左边界查找右边界
将查找右边界target转换为查找左边界target + 1
第一次尝试:
def binary_search_left_edge(nums, target):
"""查找左边界"""
# 创建左指针和右指针,左指针指向数组首元素,右指针指向数组尾元素
i, j = 0, len(nums) - 1
# 循环,直到左指针大于右指针
while i <= j:
# 计算中间元素的索引
m = (i + j) // 2
# 如果中间元素小于目标元素,说明目标元素在中间元素的右边,即区间[m + 1, j]
if nums[m] < target:
i = m + 1
# 如果中间元素大于目标元素,说明目标元素在中间元素的左边,即区间[i, m - 1]
elif nums[m] > target:
j = m - 1
# 如果中间元素等于目标元素,缩小右边界,寻找左边的目标元素
# 循环后i指向最左边的目标元素,j指向首个小于目标元素的元素
else:
j = m - 1
# 找不到目标元素时会出现两种情况
# 1.目标元素大于列表尾元素,循环后i = len(nums)
# 2.目标元素小于列表首元素,循环后i = 0,即nums[i] 不等于target
if i == len(nums) or nums[i] != target:
return -1
return i
def binary_search_right_edge(nums, target):
""""复用查找左边界的方法查找右边界"""
# 调用查找左边界的方法
j = binary_search_left_edge(nums, target + 1) - 1
# 找不到目标元素时会出现两种情况
# 1.目标元素大于列表中尾元素,循环后i指向len(nums),此时j = len(nums) - 1,即nums[j] != target:
# 2.目标元素小于列表中首元素,循环后i指向0,此时j = -1,即j == - 1
if j == -1 or nums[j] != target:
return -1
return j
if __name__ == '__main__':
nums = [1, 2, 3, 3, 3, 3, 7, 8, 9]
print(binary_search_right_edge(nums, 3))
错误:
1.未将查找左边界函数中,没有找到目标元素执行步骤删除。复用左边界查找右边界时,target + 1在数组中很可能不存在,此时左边界查找函数找不到目标元素,返回-1,进而导致右边界查找错误
def binary_search_left_edge(nums, target):
"""查找左边界"""
# 创建左指针和右指针,左指针指向数组首元素,右指针指向数组尾元素
i, j = 0, len(nums) - 1
# 循环,直到左指针大于右指针
while i <= j:
# 计算中间元素的索引
m = (i + j) // 2
# 如果中间元素小于目标元素,说明目标元素在中间元素的右边,即区间[m + 1, j]
if nums[m] < target:
i = m + 1
# 如果中间元素大于目标元素,说明目标元素在中间元素的左边,即区间[i, m - 1]
elif nums[m] > target:
j = m - 1
# 如果中间元素等于目标元素,缩小右边界,寻找左边的目标元素
# 循环后i指向最左边的目标元素,j指向首个小于目标元素的元素
else:
j = m - 1
return i
def binary_search_right_edge(nums, target):
""""复用查找左边界的方法查找右边界"""
# 调用查找左边界的方法
j = binary_search_left_edge(nums, target + 1) - 1
# 找不到目标元素时会出现两种情况
# 1.目标元素大于列表中尾元素,循环后i指向len(nums),此时j = len(nums) - 1,即nums[j] != target:
# 2.目标元素小于列表中首元素,循环后i指向0,此时j = -1,即j == - 1
if j == -1 or nums[j] != target:
return -1
return j
if __name__ == '__main__':
nums = [1, 2, 3, 3, 3, 3, 7, 8, 9]
print(binary_search_right_edge(nums, 3))
7.将查找边界问题转化为查找元素
当我们搜索元素插入位置时
1.当数组中不存在目标元素时,循环后i指向首个大于目标元素target的元素,j指向首个小于目标元素target的元素
2.所以将查找左右边界问题转化为查找,目标元素target - 0.5和target + 0.5
第一次尝试:
def binary_search_left_edge(nums, target):
"""查找左边界"""
return detail_left(nums, target - 0.5)
def detail_left(nums, target):
# 创建左指针和右指针,分别指向列表首元素和尾元素,双闭区间
i, j = 0, len(nums) - 1
# 循环,直到i > j,即左指针大于右指针
while i <= j:
# 计算中间元素的索引
m = (i + j) // 2
# 如果中间元素小于target,则插入位置在区间[m + 1, j]中
if nums[m] < target:
i = m + 1
# 如果中间元素大于target,则插入位置在区间[i, m - 1]中
elif nums[m] > target:
j = m - 1
# 循环后,返回插入位置i
return i
def binary_search_right_edge(nums, target):
"""查找右边界"""
return detail_right(nums, target + 0.5)
def detail_right(nums, target):
# 创建左指针和右指针,分别指向列表首元素和尾元素,双闭区间
i, j = 0, len(nums) - 1
# 循环,直到i > j,即左指针大于右指针
while i <= j:
# 计算中间元素的索引
m = (i + j) // 2
# 如果中间元素小于target,则插入位置在区间[m + 1, j]中
if nums[m] < target:
i = m + 1
# 如果中间元素大于target,则插入位置在区间[i, m - 1]中
elif nums[m] > target:
j = m - 1
# 循环后,返回插入位置i
return j
if __name__ == '__main__':
nums = [1, 2, 3, 3, 3, 6, 7, 8, 9]
print(binary_search_left_edge(nums, 3))
print(binary_search_right_edge(nums, 3))