本节内容只有通过例题来记录效果才是最好的,请看下面内容!
递归实现二分法
经典二分查找问题:LintCode 炼码
描述**:**在一个排序数组中找一个数,返回该数出现的任意位置,如果不存在,返回 -1。 输入:nums = [1,2,2,4,5,5], target = 2 输出:1 或者 2
     def findPosition(self, nums, target):
        def binarysearch(nums,start,end,target):
            middle = (start+end)//2
            # if((start==end and nums[start]!=target) or start>end):
                # return -1
            if(start>end): #这里是>还是>= ? 留给自己思考!
                return -1
            if(nums[middle]==target):
                return middle
            if(nums[middle]<target):
                return binarysearch(nums, middle+1, end, target)
            else:
                return binarysearch(nums, start, middle-1, target)
        # write your code here
        if(nums==[] or nums == None):
            return -1
        return binarysearch(nums, 0, len(nums)-1, target) 
非递归实现二分法
请注意,非递归二分法非常常用,尤其在双指针里面。但是这类题目的一个难点就是搞不拎清到底是'>='('<=')还是'>'('<'),到底是 left = middle + 1 (right = middle - 1)还是 left = middle (right = middle)。那么,本章节将会彻底讲明白这些坑,并给出一个万能二分模板解决一系列问题。在进行下面的例题讲解时,先搞清楚这几种循环结束时条件的值(假设都是 +=1):while(left<right)、while(left<=right)、while(left+1<right),第一种结束时 left==right ,第二种是left = right + 1,第三种是 left ==right - 1;
经典二分查找问题:LintCode 炼码
描述**:**在一个排序数组中找一个数,返回该数出现的任意位置,如果不存在,返回 -1。 输入:nums = [1,2,2,4,5,5], target = 2 输出:1 或者 2 使用经典非递归算法实现:
def findPosition(nums, target): 
    # write your code here
    if(nums==[] or nums == None):
        return -1
    left = 0
    right = len(nums)-1
    while(left<=right): # 这里是'<='还是 '<' ?
        middle = (left+right)//2
        if(nums[middle]==target):
            return middle
        if(nums[middle]<target):
            left = middle+1
            if(nums[middle]>target):
                right = middle-1
    #if(nums[left]==target):
    #    return left;
    return -1 
解释:那么上述代码中第9行到底要不要加上'='呢?答案是既可以使用"<=",也可以使用"<";这里举例说明,假设nums = [3] 只有一个元素,那么left = 0 ,right = 0 。如果加上"="号那么,这种情况依旧可以进入while中进行判断,也就是说while循环已经conver到了这种情况,所以可以加上。如果不加"=",那么需要单独判断这种特例,那就需要17,18行的代码。总结就是,当遇到这种情况搞不清楚时,举一个只有一个元素数组的特例,看看是否当用"<="时 while 循环能否cover到取等于的情况,如果能就加上等号,如果不能就针对特例另外写判断。一般建议写不加等于号的情况,将等于的情况单独拿出来判断,这样可以避免很多边界值问题。好的,接下来是难度大一点的二分查找,这里特别注意边界条件!不然,很容易错,可以先自己做做再看解析: 目标最后位置:LintCode 炼码 **描述:**给一个升序数组,找到 target 最后一次出现的位置,如果没出现过返回 -1 输入:nums = [1,2,2,4,5,5], target = 2 输出:2
    def last_position(self, nums: List[int], target: int) -> int:
        # write your code here
        if(nums==None or nums==[]):
            return -1
        left=0;right=len(nums)-1
        while(left+1<right):
            middle = (left+right)//2
            if(nums[middle]==target):
                left = middle   # 不能是middle+1,这样会跳过middle
            if(nums[middle]<target):
                left = middle+1
            if(nums[middle]>target):
                right = middle-1
        if(nums[right]==target):
            return right
        if(nums[left]==target):
            return left
        return -1 
解析:这道题是要找到最后一个等于target值的数字的下标,这个时候只需要将第11行代码直接返回中间值改成让left = middle,也就是说当找到一个中间值等于target时我们并不直接返回,而是包含这个middle继续向后查找。注意,这里的判断条件必须要写出 left+1<right,不然当nums=[2,2],target=2时,则进入死循环(请自觉试试),第8行这样写的话,我们希望left = right -1时,也就是left与right相邻时结束。所以,最后第16到19行代码就必须单独判断这两个索引是否满足题意。当你会解这道题时,你应该会解下一道题: fisrt_position二分查找:LintCode 炼码 **描述:**给定一个排序的整数数组(升序)和一个要查找的整数 target,用O(logn)O(logn)的时间查找到target第一次出现的下标(从0开始),如果target不存在于数组中,返回-1。
 解析:你只需要更改第11、16到19行代码即可完成!
最后给出一个二分查找题目的通用模板:
    def last_position(self, nums: List[int], target: int) -> int:
        # write your code here
        if(nums==None or nums==[]):
            return -1
        left=0;right=len(nums)-1
        while(left+1<right): # 保证循环一定会退出
            middle = (left+right)//2
            if(nums[middle]==target):
                left = middle   # 根据题意修改这里
            if(nums[middle]<target):
                left = middle  #不 +1 ,不影响结果 ,有时候+1反而错误
            if(nums[middle]>target):
                right = middle #不 -1 ,不影响结果 ,有时候-1反而错误
        # 因为循环提前一步退出了,所以最后一步自己判断
        if(nums[right]==target):
            return right
        if(nums[left]==target):
            return left
        return -1 
使用通用目标解决以下问题: 排序数组中最接近元素:LintCode 炼码 **描述:**在一个排好序的数组 A 中找到 i 使得 A[i] 最接近 _target _如果数组中没有元素,则返回-1。

585· 山脉序列中的最大值:LintCode 炼码 **描述:**给 n 个整数的山脉数组,即先增后减的序列,找到山顶(最大值)。
from typing import (
    List,
)
class Solution:
    """
    @param nums: a mountain sequence which increase firstly and then decrease
    @return: then mountain top
    """
    def mountain_sequence(self, nums: List[int]) -> int:
        # write your code here
        if(nums==[] or nums==None):
            return []
        left = 0
        right = len(nums)-1
        while(left+1<right):
            middle = (left+right)//2
            if(nums[middle]>nums[middle+1]):
                right = middle
            else:
                left = middle
        return max(nums[left],nums[right]) 
62 · 搜索旋转排序数组:LintCode 炼码
    def search(self, a: List[int], target: int) -> int:
        # write your code here
        if(a==[] or a == None):
            return -1
        # 有一半的数一定是单调递增的,在单调的那边找就行
        left=0
        right=len(a)-1
        while(left+1<right):
            middle = (left+right)//2
            if(a[middle]>=a[left]):
                if(target>=a[left] and target<=a[middle]):
                    right = middle 
                else:
                    left = middle 
            if(a[middle]<a[right]):
                if(target>=a[middle] and target<=a[right]):
                    left = middle
                else:
                    right = middle
        if(a[left]==target):
            return left
        elif(a[right]==target):
            return right
        else:
            return -1 
未排序的序列上的二分法
75 · 寻找峰值:LintCode 炼码 **描述:**给定一个整数数组(size为n),其具有以下特点:
- 相邻位置的数字是不同的
 - A[0] < A[1] 并且 A[n - 2] > A[n - 1]
 
假定_P_是峰值的位置则满足A[P] > A[P-1]且A[P] > A[P+1],返回数组中任意一个峰值的位置。 输入:A = [1, 2, 1, 3, 4, 5, 7, 6] 输出:1
    def find_peak(self, a: List[int]) -> int:
        # write your code here
        if(a==[]):
            return -1
        left = 0 
        right = len(a)-1
        while(left+1<right):
            middle =(left+right)//2
            if(a[middle]>a[middle-1]):
                left = middle
            else:
                right = middle
        if(a[left]>a[right]):
            return left
        else:
            return right 
在答案集合上进行二分
第一步:确定答案范围,第二步:验证答案大小 183 · 木材加工:LintCode 炼码 **描述:**有一些原木,现在想把这些木头切割成一些长度相同的小段木头,需要得到的小段的数目至少为 k。给定L和k,你需要计算能够得到的小段木头的最大长度。 输入: L = [232, 124, 456] k = 7 输出: 114 说明: 我们可以把它分成 114 的 7 段,而 115 不可以 ,对于 124 这根原木来说多余的部分没用可以舍弃,不需要完整利用
def wood_cut(self, l: List[int], k: int) -> int:
        # write your code here
        if(l==[]):
            return 0
        def cut(l,length):
            count = 0
            for i in l:
                count +=(i//length)
            return count
        start = 1
        end = sum(l)//k
        if(end<1):
            return 0
        while(start+1<end):
            middle = (start+end)//2
            if(cut(l,middle)>=k):
                start = middle
            else:
                end = middle
        if(cut(l,end)>=k):
            return end
        elif(cut(l,start)>=k):
            return start
        else:
            return -1 



















