算法之二分法

二分查找法整理(题解与思路)
力扣例题35
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0
来源(https://leetcode-cn.com/problems/search-insert-position/)


力扣69. x 的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842...,
     由于返回类型是整数,小数部分将被舍去。
来源(https://leetcode-cn.com/problems/sqrtx/)

思路借鉴力扣里大佬的二分法理解,进行的总结(https://leetcode-cn.com/u/liweiwei1419/)
传统二分法模版存在的问题:
    1、mid = (left+right)/2 当right很大的时候,容易超出整形类型的界限。
    2、使用while left<=right: 只有left > right 的时候才会退出,此时就要根据要求是返回left还是right。
神奇的二分模版总结:
    神奇的二分法的最最最最最基本思想:"夹逼法" 或者 “排除法”
    排除法:每一轮的循环之后,都需要排出一半的元素。

    循环条件选择:while left<right:
        原因:因为只有这样,在跳出循环后,只能是left=right,此时就不需要考虑是返回left还是right。
    
    求mid的方法:mid = left+(right-left) // 2 或者 mid = (right+left) >> 1 (最佳选择)
        原因: mid = (left+right)/2 当right非常大的时候,可能超出整数型的界限。
              mid = left+(right-left) // 2 当right非常大,且left为负数的时候,也可能超出整数型的界限
              mid = (right+left) >> 1 (无符号右移)的时候,即使超出了界限,但是最终的结果并没有改变。


    选取判断的条件:
        把自己肯定的排出在外。 比如,一个女人在选择男朋友的时候会对男朋有选择的标准作出选择"有车、有房的可以考虑,但是没有钱的可定不是"
    所以在选择条件的时候,将自己肯定不包含最后所需要的元素排出在外。
        比如力扣的35题,搜索插入位置,根据题意可以了解到,我们所需要的条件是将目标元素插入到一个排序列表中:可以理解为当目标元素大于列表
    中的最后一个元素,那么就将返回列表的长度,否则就将目标元素插入到列表中大与或者等于目标元素的第一个位置。由此我们可以判断目标元素一定不会插入到左边。那么我们就可以将判断条件写成 if target > a[i]: left = mid+1 。
        所以选取if判断的时候只需要两条判断,将其肯定不在的写入判断中。

    选取左中位数还是有中位数:
        左中位数 : mid = left+(right-left) // 2
        右中位数:  mid = left+(right-left+1) // 2
        在二分法中,选取中位数非常的关键,如果选择不好中位数,会导致程序进入死循环,或者出错。
        目标元素一定要包含在要所截取的列表中,所以选取左中位数或者有中位数有一个很好的判定方法,便是通过最后目标元素是在左边还是右边来判断。
        当目标元素肯定不在左边的时候,便选取左中位数;因为右边一定包含着目标元素,此时距需要left = mid+1;
        当目标元素肯定不在右边的时候,便选取有中位数。

    以力扣35题为例子,我们便可以通过上述的方法编写代码
   

class Solution(object):
        def searchInsert(self, nums, target):
            if len(nums) == 0:
                return 0    # 特殊情况,特殊判定

            left = 0
            right = len(nums) # 为什么是len(nums) 而不是len(nums) - 1 ;因为当目标值大于列表中的最后一个元素时,此时应该插入到len(nums)
            while left < right:   # 此时可以避免出现判断最后是返回left还是right
                mid = (left+right) >> 1  # 选取左中位数,因为目标元素所插入的索引一定不在左边届中
                if target > nums[mid]:
                    left = mid + 1        # 因为目标元素的索引一定是大于左中位数的,
                else:
                    right = mid
            return left

    力扣69题
        

'''
    此题我们可以肯定的是,目标元素一定不在右边,因为a**2大x一定不是目标元素。故选择右中位数,判定条件为
    if(mid**2 > x):
        right = mid -1
'''
class Solution(object):
    def mySqrt(self, x):
        """
        :type x: int
        :rtype: int
        """
        left = 0
        right = x        
        while left < right:
            mid = (left+right+1) >> 1
            mid_2 = mid ** 2
            if mid_2 > x:
                right = mid - 1
            else:
                left = mid
        return left
posted @ 2019-11-12 22:18  虚xu  阅读(925)  评论(0编辑  收藏  举报