every blog every motto: Although the world is full of suffering, it is full also of the overcoming of it
0. 前言
一题搞清楚二分区间问题—闭区间、左闭右开、左开右闭、全开区间
0.1 题目:Problem: 441. 排列硬币
你总共有 n 枚硬币,并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯,其第 i 行必须正好有 i 枚硬币。阶梯的最后一行 可能 是不完整的。给你一个数字 n ,计算并返回可形成 完整阶梯行 的总行数。
计算m行对应的硬币数,由于是等差数列, ( 首项 + 尾项) ∗ 项数 / 2 (首项+尾项)*项数/2 (首项+尾项)∗项数/2 ,即:
( 1 + m ) ∗ m / 2 (1+m)∗m/2 (1+m)∗m/2
总硬币数为n,所以我们需要比较两个的大小,即,满足
( 1 + m ) ∗ m / 2 < = n (1+m)∗m/2<=n (1+m)∗m/2<=n
变换后:
( 1 + m ) ∗ m < = 2 ∗ n (1+m)∗m<=2∗n (1+m)∗m<=2∗n
即通过二分法不断尝试m的可能取值。
敲重点:
while 循环条件决定是闭区间还是开区间
while 循环条件决定是闭区间还是开区间
while 循环条件决定是闭区间还是开区间
1 闭区间
闭区间,m是向上取整还是向下取整都可以,
由于是闭区间,m对应的值是可以取到的
由于是闭区间,m对应的值是可以取到的
由于是闭区间,m对应的值是可以取到的
所以,不管left 还是right的更新,都需要在m的基础上加一或是减一
举个例子:当区间为【2,2】,不满足条件时,需要更新left或right,如果此时left = m,那么就陷入了死循环;right同理。
class Solution:def arrangeCoins(self, n: int) -> int:l, r = 1, n while l <= r: # 闭区间m = (l + r )//2 #·向下取整,eg:·(3+4)//2·=>·3# m = (l + r +1)//2 if m * (m+1) <= 2*n:l = m + 1else:r = m - 1# m = (l + r + 1)//2 # 向上取整,eg (3+4+1)//2 => 4return l - 1
2 左闭右开【)
常规的默认写法是 m = (l+r)//2,这里蕴含着向下取整在里面,所以这个对应的是左闭右开。
故,当更新时,l = m+ 1 (若,当区间为【3,4】,m = (3+4)//2 = 3,l = m = 3,区间没有变化,死循环);
而 r= m ,
class Solution:def arrangeCoins(self, n: int) -> int:l, r = 1, n while l < r: # 左闭右开区间 [)m = (l + r )//2 # 向下取整,eg: (3+4)//2 => 3if m * (m+1) <= 2*n:l = m + 1else:r = m return l - 1 if n != 1 else 1
3 左开右闭(】
由于m是向上取整,取右区间对应的值,eg: 区间为[3,4],m = (3+4+1)//2 = 4,
右区间是取到的,所以当更新时:r = m - 1 (如果r = m,即r = 4,区间没有变化,死循环);
而左区间取不到,所以 l = m
class Solution:def arrangeCoins(self, n: int) -> int:l, r = 1, n while l < r: # 向上取整,不加1是向下取整# 左开右闭区间 (]m = (l + r +1 )//2 # 向上取整,eg (3+4+1)//2 => 4if m * (m+1) <= 2*n:l = m else:r = m - 1return l
4 全开区间
- 全开区间的默认条件写法是:
l +1 < r
- 初始条件是两个不满足条件的值:
l, r = 0, n + 1
- 由于是开区间,m,向下还是向上取整都是可以的,和闭区间类似
- 由于是left和right的更新
l = m 或
r = m`
class Solution:def arrangeCoins(self, n: int) -> int:l, r = 0, n + 1 # 注意初始值while l + 1 < r: # 全开区间 ()m = (l + r )//2# m = (l + r +1)//2if m * (m+1) <= 2*n:l = melse:r = m return l# m = (l + r + 1)//2
5 总结
5.1 开区间还是闭区间,有while条件决定
* <=是闭区间
* < 开区间,具体哪种由mid的取值决定* m = (left + right)//2,向下取整,所以left能够取到,左闭右开 `[)`* m = (left + right + 1)//2, 向上取整,right能够取到,左开右闭 `(]`
5.2 left 和right的更新
* 当闭区间【】时,left = m + 1; right = m - 1
* 当全开区间()时,left = m ; right = m
* 当左闭右开【)时,left = m + 1; right = m
* 当左开右闭(】时,lefet = m; right = m - 1
5.3 m值的计算
5.3.1 闭区间
向上向下取整都可以
m = (l + r +1)//2
m = (l + r )//2
5.3.2 左闭右开【)
m = (l + r )//2 # 向下取整,eg: (3+4)//2 => 3
5.3.3 左开右闭(】
m = (l + r + 1)//2 # 向下取整,eg: (3+4+1)//2 => 4
5.3.4 全开区间()
都可以,同闭区间
m = (l + r +1)//2
m = (l + r )//2