在正式写总结前,我想说一下我踩过的那些坑 ,当时脑袋都要晕掉了,于是花了两天时间搞明白了二分法,写了这篇总结。
我踩过的那些坑: 之前采用while left <= right,经常遇到死循环。而且代码分支很多,经常考虑不到特殊情况,最后把自己绕晕了到底有多少种特殊情况。两天得此总结。
二分法【三步走】方法
1、先确定边界。 一般情况下left=0,right=len(nums)-1是数组的索引值,但是如果可能遇到答案是含有最大索引值+1的情况(参考leetcode35题),那么right=len(nums)。
2、while循环部分:然后确定选择左中位数和右中位数(最重要)。 一般情况下,看中位数所在的值是不是通过left和right的移动能完全排除,如果都可以完全排除,那选择左中位数和右中位数都可以(参考leetcode704),如果不能完全排除,看左中位数移动left能不能完全排除(看有中位数移动right能不能完全排除)。而且如果选择左中位数,mid = (left + right)/2 left = mid + 1 right = mid;选择右中位数,mid=(left+right+1)/2 right = mid - 1 left = mid。很多情况下,使用左中位数会出现死循环,此时mid=right,解决的方法就是采用右中位数。
实践经验:一般情况下,【1】先选择左中位数,看nums[mid]是不是在第一个if逻辑中完全排除即可以按照left=mid+1,【2】再用两个数的数组验证一下会不会造成死循环,如果不会,那这样ok的。如果进入死循环,那选择右中位数,同时mid = (left + right + 1) // 2 并且 right = mid - 1
3、返回left值前,先看看值在不在数组里 ,如果一定在,那就不用判断直接返回left,如果不一定在数组里,那么需要判断一下这个值是不是要找的那个,如果不是返回-1。
基本思想
(1)首先把循环可以进行的条件写成 while(left < right),在退出循环的时候,一定有 left == right 成立,此时返回 left 或者 right 都可以
或许你会问:退出循环的时候还有一个数没有看啊(退出循环之前索引 left 或 索引 right 上的值)? 没有关系,我们就等到退出循环以后来看,甚至经过分析,有时都不用看,就能确定它是目标数值。更深层次的思想是“夹逼法”或者称为“排除法”。
最重点的要注意:在确定核心逻辑时,应该这样想,看mid是否完全排除,如果是 left = mid+1,照这个思路就可,放心,不会错。(此时可能你看不懂,但是看完全文就懂了)
(2)“神奇的”二分查找法模板的基本思想(特别重要)
“排除法”即:在每一轮循环中排除一半以上的元素,于是在对数级别的时间复杂度内,就可以把区间“夹逼” 只剩下 1 个数,而这个数是不是我们要找的数,单独做一次判断就可以了。
“夹逼法”或者“排除法”是二分查找算法的基本思想,“二分”是手段,在目标元素不确定的情况下,“二分” 也是“最大熵原理”告诉我们的选择。
接下来,让我先甩出来二分法模板!
二分法模板:
def search(nums, target):
if nums is None or len(nums) == 0:
return -1
left = 0
right = len(num)-1 #【第一步需要判断的】
while left < right:
mid = (left + right) // 2 #【第二步需要判断的】
#mid = (left + right +1) // 2
if nums[mid] > target:#条件,需要逻辑判断,此时要避免死循环
left = mid + 1
else:
right = mid
return left #【第三步要思考的】看是否要判断这个if nums[left] == target
基本模板就是这样,接下来就是实践,积累经验。
目前做过的题有:704、69、34、35、153、154、33、81、4
推荐按照这个顺序做题
下面有这些题的答案(我自己写的,还有很多不足之处,欢迎讨论)
leetcode[704二分查找]——常规法
题目 :给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
下面代码是用左中位法:
class Solution:
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1 #第一步:判断返回值是不是能超出边界,此题不会超出边界
while left < right:
mid = (left + right) // 2 #第二步:判断选择左中位法还是右中位法,对于有序二分法查找,选择左中位法和又中位法都可以
if nums[mid] < target: #找能排除mid的逻辑
left = mid + 1
else:
right = mid
if nums[left] == target: #第三步,对nums[left]进行判断
return left
else:
return -1
704小结 :这是最常规的