看二分法的一句代码,很有意思:
int mid = low + ((high - low) / 2);
这个与二分法求两数平均值的结果是一样的,为什么要这样写呢?
查阅资料后很好理解,这是为了避免数值溢出的问题。如果直接按照原来的思路来写:
int mid = (low + high) / 2;
C++本身对int类型的字符就有4Bytes(32位)的存储上限要求,也就是说int类型的字符的取值范围在-2^32~(2^32-1)。如果low和high本身就达到范围值了怎么办?就会造成报错溢出。
平时习惯用Python写,Python3避免了这个问题,只要不超出内存范围,就不需要Codinger设计担心这个问题。
有好的习惯,还可以写成:
int mid = (low+high)>>>1;
值得注意的是,这样写虽然可以避免溢出,但是会造成结果错误的情况:
int mid = high/2+ low/2;
当high或者low中两者都为奇数时,结果会错。
eg:high=3 low=5 则mid本应该等于4,但是这种算法算出来为1+2=3!!
对二分法的好处理(骚操作)有所理解后,看到了Leetcode 69 sqrt(x) 这道题目。本题目不能用内置函数来找到数的完全平方数的整数部分。起初是想建立一个HashMap,把x映射到x的平方上,然后再依次比较大小。但这个方法对十分大的数,尤其是Python的内存边界数无能为力。而且对于很大的数字必定会造成搜索空间巨大而超过时间限制。
一筹莫展之时,忽然发现其实这个就是查询问题的套路:给定某一个查询条件(这里就是平方不大于x),再给定一个搜索范围(0~x),再其中找到符合条件的数。稍微不同的是之前说的二分查找就是直接等于这个要找到的数,但是这里所说的整数部分解,其实就是最接近却又不大于x的数,其实就是个判断语句的条件不同而已,殊途同归。
故手撕代码如下:
left,right=0,x
while left<right:
mid=left+(right-left+1)//2
if mid*mid<=x:
left=mid
else:
right=mid-1
return left
其中值得注意的边界条件有三点:
- left<right而不是left<=right
- left=mid而right=mid-1
- mid=left+(right-left+1)//2这里的1
结合Leetcode 35 Search Insert Position来看,如果列表中存在这个数就返回这个数的位置,如果不存在就返回大小顺序排列应该放的位置。发现也是一个给定了搜索条件和搜索范围的二分查找题目。
pointer1,pointer2=0,len(nums)-1
while pointer1<=pointer2:
mid=pointer1+(pointer2-pointer1)//2
if nums[mid]==target: return mid
elif nums[mid]>target:
pointer2=mid-1
else:
pointer1=mid+1
return pointer1
这里的边界条件和Leetcode 69就完全不同了。仔细思考(抠脑壳)之后的tips:
- 二分法的问题可以分类为[left,right)和[left,right]两种答案区间。
- [left,right)可以直接代入[2,2),这种情况是错误的,所以跳出循环的条件就是left<right,必须是[2,3)才正确。而[left,right]的[2,2]是存在的,所以跳出循环的条件应该是left<=right。
- 由上,同理,最后快结束循环的时候,假如[2,4]和[2,4),mid=3,如果执行left=mid+1,那就变成[4,4]和[4,4),可以看出再left<right的情况下,不能将mid+1来更新left指针。
未完待续。。。