练13:二分法

发布于:2024-12-20 ⋅ 阅读:(8) ⋅ 点赞:(0)

欢迎大家订阅【蓝桥杯Python每日一练】 专栏,开启你的 Python数据结构与算法 学习之旅!


1 二分查找

①定义
在计算机科学中,二分算法(Binary Search)是一种高效的查找算法,用于在一个已排序的数组中查找某个目标元素。二分算法的基本思想是通过将问题规模每次减半,来缩小查找范围,从而实现高效搜索。

②原理
二分查找算法基于分治思想。给定一个已排序的数组,二分算法通过不断将数组分为两部分,比较目标值与中间元素的关系,来确定目标值所在的范围。

③算法步骤

  1. 初始化边界:设定两个指针,leftright,分别指向数组的起始和结束位置。
  2. 计算中间位置:在每次查找中,计算中间位置 mid = (left + right) // 2
  3. 比较目标与中间值
    • 如果目标值等于中间元素 array[mid],则查找成功,返回该元素的索引。
    • 如果目标值小于中间元素,目标元素只能在左半部分,因此将右边界 right 更新为 mid - 1
    • 如果目标值大于中间元素,目标元素只能在右半部分,因此将左边界 left 更新为 mid + 1
  4. 终止条件:当 left > right 时,查找失败,目标元素不在数组中。

④时间复杂度分析

二分查找算法的时间复杂度为 O(log n),其中 n 是数组的元素个数。原因在于,每次查找时都会将搜索区间减半。因此,二分查找的效率非常高,尤其适用于大规模的数据集。

⑤空间复杂度分析

  • 非递归实现的空间复杂度为 O(1),因为只使用了常数级的额外空间。
  • 递归实现的空间复杂度为 O(log n),因为递归调用会占用栈空间。

⑤应用场景

二分查找算法在许多实际问题中都有广泛的应用,尤其是当数据集已经排好序时。常见的应用场景包括:

  1. 查找数据:二分查找常用于在已排序的数组中查找元素,尤其是当元素数量很大时,二分查找能够显著提高查找效率。
  2. 插入位置:在一些算法中,我们需要找出某个元素在一个已排序数组中的插入位置,这时二分查找是一种高效的方法。
  3. 区间问题:二分查找还可以用来解决一些特殊的区间问题,比如寻找满足某个条件的最小值或最大值。
  4. 搜索引擎与数据库:搜索引擎和数据库在对大量有序数据进行检索时,通常会用到二分查找。

在这里插入图片描述

2 例题分析

在这里插入图片描述

题目地址:https://www.lanqiao.cn/problems/99/learning/

样例输入

2 10
6 5
5 6

样例输出

2

【示例代码】

# 输入巧克力块的数量N和需要切出的块的数量K
N, K = map(int, input().split())  
a = []

for i in range(N):
    # 输入每个巧克力的长宽
    x, y = map(int, input().split())  
    # 将巧克力的长宽作为元组存储到列表a中
    a.append((x, y))  

# check函数:判断给定边长x时,是否能够切出至少K块巧克力小块
def check(x):
    # cnt表示当前边长x下,能够切出的块的总数
    cnt = 0  
    # 遍历每一块巧克力
    for H, W in a: 
         # 计算当前巧克力中可以切出多少个边长为x的小块 
        cnt += (H // x) * (W // x)  
    # 如果切出的块数大于等于K,则返回True,表示可能
    return cnt >= K  
    
# 二分查找边长x的最大值
left, right = 1, 100000  # 边长x的搜索范围,从1到100000
ans = 1  # 初始假定最小的有效边长为1

while left <= right:
    mid = (left + right) // 2  # 取当前搜索区间的中点
    if check(mid):  # 如果可以切出至少K块小块
        ans = mid  # 记录当前中点作为可能的答案
        # 尝试寻找更大的边长,调整搜索区间为[mid+1, right]
        left = mid + 1  
    else:
        # 否则尝试更小的边长,调整搜索区间为[left, mid-1]
        right = mid - 1  
print(ans)  # 输出最大边长

【执行流程】

①初始输入

N, K = map(int, input().split())  # 输入巧克力块的数量N和需要切出的块的数量K
a = []

for i in range(N):
    # 输入每个巧克力的长宽
    x, y = map(int, input().split())  
    # 将巧克力的长宽作为元组存储到列表a中
    a.append((x, y))
  • 输入的 2 10 解析为 N = 2K = 10,表示有 2 块巧克力,目标是切出 10 块小巧克力。
  • 接下来,输入两行表示巧克力的尺寸:6 55 6
  • a 列表最终存储为 [(6, 5), (5, 6)],即两块巧克力的尺寸。

②定义 check(x) 函数

def check(x):
    # cnt表示当前边长x下,能够切出的块的总数
    cnt = 0  
    # 遍历每一块巧克力
    for H, W in a: 
         # 计算当前巧克力中可以切出多少个边长为x的小块 
        cnt += (H // x) * (W // x)  
    # 如果切出的块数大于等于K,则返回True,表示可能
    return cnt >= K
  • check(x) 的作用是,给定边长 x,判断是否能够切出至少 K 块小巧克力。
  • 对于每一块巧克力 (H, W),计算在该巧克力中能够切出的边长为 x 的小块数目为 (H // x) * (W // x),即在高度和宽度上都切割出多少个小块。
  • 最后,返回 cnt >= K,表示切出的块数是否达到或超过了 K

③二分查找最大边长

left, right = 1, 100000  # 边长x的搜索范围,从1到100000
ans = 1  # 初始假定最小的有效边长为1

while left <= right:
    mid = (left + right) // 2  # 取当前搜索区间的中点
    if check(mid):  # 如果可以切出至少K块小块
        ans = mid  # 记录当前中点作为可能的答案
        # 尝试寻找更大的边长,调整搜索区间为[mid+1, right]
        left = mid + 1  
    else:
        # 否则尝试更小的边长,调整搜索区间为[left, mid-1]
        right = mid - 1  
print(ans)  # 输出最大边长
  • 设定初始的搜索范围为 left = 1right = 100000,表示我们假定边长 x 可能的最大值为 100000
  • 使用二分查找逐步缩小搜索范围来找到能切出至少 K 块小巧克力的最大边长 x

【执行二分查找过程】

初始区间 [left=1, right=100000]

第一次循环

  • 计算中点:mid = (1 + 100000) // 2 = 50000
  • 调用 check(50000)
    • 对于第一块巧克力 (6, 5)50000 边长无法切割出任何小块(6 // 50000 = 05 // 50000 = 0),所以结果为 0 块。
    • 对于第二块巧克力 (5, 6),同样,50000 边长无法切割出任何小块(5 // 50000 = 06 // 50000 = 0),所以结果为 0 块。
    • 总共切出的块数为 0check(50000) 返回 False,因此调整搜索区间为 [1, 49999]

第二次循环

  • 计算中点:mid = (1 + 49999) // 2 = 25000

  • 调用 check(25000)

    • 对于第一块巧克力 (6, 5)25000 边长无法切割出任何小块(6 // 25000 = 05 // 25000 = 0),所以结果为 0 块。
    • 对于第二块巧克力 (5, 6),同样,25000 边长无法切割出任何小块(5 // 25000 = 06 // 25000 = 0),所以结果为 0 块。
    • 总共切出的块数为 0check(25000) 返回 False,因此调整搜索区间为 [1, 24999]
  • 继续减小搜索区间:直到找到一个合适的边长。

终止条件与最终结果

最终,通过多次二分查找,我们最终会找到 x = 2 是最大边长,能够切出至少 K = 10 块小巧克力。具体的结果是:

  • 对于 x = 2,第一块巧克力 (6, 5) 可以切出 6 // 2 = 3 行,5 // 2 = 2 列,总共能切出 3 * 2 = 6 块。
  • 对于 x = 2,第二块巧克力 (5, 6) 可以切出 5 // 2 = 2 行,6 // 2 = 3 列,总共能切出 2 * 3 = 6 块。
  • 总共切出的块数是 6 + 6 = 12 块,满足 K = 10,因此 check(2) 返回 True,最终输出 2

④最终输出

2

【运行结果】
在这里插入图片描述