算法之二分法


二分查找法整理(題解與思路)
力扣例題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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM