Leetcode中幾道二分查找(Binary Search)的算法題總結


二分查找也稱折半查找(Binary Search),它是一種效率較高的查找方法。但是,折半查找要求線性表必須采用順序存儲結構,而且表中元素按關鍵字有序排列。二分查找法的時間復雜度是對數級別的,O(log2n)

public int binarySearch(int [] array, double key) {
  int l = 0;
  int r = array.length - 1;
  while (l <= r) {
    int m = (l + r)/ 2;
    if (key == array[m])
      return m;
    else if (key < array[m])
      r = m - 1;
    else
      l = m + 1;
  }
  return l;
}

如果key在array中,返回的是key在array中的位置,如果不在array中,返回的是key應該插入的位置也就是第一個大於key的位置。這里和java.util.Arrays類返回值-(low + 1)不太一樣,個人覺得我這種寫法做某些算法題更方便。

1. Sqrt(x)

實現 int sqrt(int x) 函數。計算並返回 x 的平方根。x 保證是一個非負整數。

分析:

這道題有兩種解法,二分法和擬牛頓法

二分法 

class Solution(object):
    def mySqrt(self, x):
        """
        :type x: int
        :rtype: int
        """
        l = 0
        r = x // 2 + 1
        while l <= r:
            m = (l + r) // 2
            if m ** 2 <= x and (m + 1) ** 2 > x:
                return m
            elif m ** 2 > x:
                r = m - 1
            else:
                l = m + 1

牛頓法

class Solution:
    def mySqrt(self, x):
        """
        :type x: int
        :rtype: int
        """
        r = x
        while r*r > x:
            r = (r + x//r) // 2
        return r

 

2.Search in Rotated Sorted Array I 、II

假設按照升序排序的數組在預先未知的某個關鍵點上旋轉。

(即 0 1 2 4 5 6 7 將變成 4 5 6 7 0 1 2)。

給你一個目標值來搜索,如果數組中存在這個數則返回它的索引,否則返回 -1。

你可以假設數組中不存在重復。

分析:

這是二分查找的一道變形題,因為rotate的緣故,當我們切取一半的時候可能會出現誤區,所以我們要做進一步的判斷。具體來說,假設數組是A,每次左邊緣為l,右邊緣為r,還有中間位置是m。在每次迭代中,分三種情況:
(1)如果target==A[m],那么m就是我們要的結果,直接返回;
(2)如果A[m]<A[r],那么說明從m到r一定是有序的(沒有受到rotate的影響),那么我們只需要判斷target是不是在m到r之間,如果是則把左邊緣移到m+1,否則就target在另一半,即把右邊緣移到m-1。
(3)如果A[m]>=A[r],那么說明從l到m一定是有序的,同樣只需要判斷target是否在這個范圍內,相應的移動邊緣即可。
class Solution(object):
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        l = 0
        r = len(nums) - 1
        while l <= r:
            m = (l + r) // 2
            if nums[m] == target:
                return m
            if nums[m] < nums[r]:
                if target > nums[m] and target <= nums[r]:
                    l = m + 1
                else:
                    r = m - 1
            else:
                if target < nums[m] and target >= nums[l]:
                    r = m - 1
                else:
                    l = m + 1
        return -1

  follow up:

如果數組元素允許重復,怎么辦?

這會影響到程序的時間復雜度嗎?會有怎樣的影響,為什么?

分析:

和Search in Rotated Sorted Array唯一的區別是這道題目中元素會有重復的情況出現。不過正是因為這個條件的出現,出現了比較復雜的case,甚至影響到了算法的時間復雜度。原來我們是依靠中間和邊緣元素的大小關系,來判斷哪一半是不受rotate影響,仍然有序的。而現在因為重復的出現,如果我們遇到中間和邊緣相等的情況,我們就丟失了哪邊有序的信息,因為哪邊都有可能是有序的結果。假設原數組是{1,2,3,3,3,3,3},那么旋轉之后有可能是{3,3,3,3,3,1,2},或者{3,1,2,3,3,3,3},這樣的我們判斷左邊緣和中心的時候都是3,如果我們要尋找1或者2,我們並不知道應該跳向哪一半。解決的辦法只能是對邊緣移動一步,直到邊緣和中間不在相等或者相遇,這就導致了會有不能切去一半的可能。所以最壞情況(比如全部都是一個元素,或者只有一個元素不同於其他元素,而他就在最后一個)就會出現每次移動一步,總共是n步,算法的時間復雜度變成O(n)。代碼如下:

class Solution:
    def search(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: int
        """
        if not nums:
            return False
        l = 0
        r = len(nums) - 1
        while l <= r:
            m = (l + r) // 2
            if nums[m] == target:
                return True
            if nums[m] < nums[r]:
                if target > nums[m] and target <= nums[r]:
                    l = m + 1
                else:
                    r = m - 1
            elif nums[m] > nums[r]:
                if target < nums[m] and target >= nums[l]:
                    r = m -1
                else:
                    l = m + 1
            else:
                r -= 1
        return False

 

3.Find Minimum in Rotated Sorted Array I 、II

假設一個按照升序排列的有序數組從某未知的位置旋轉。

(比如 0 1 2 4 5 6 7 可能變成 4 5 6 7 0 1 2)。

找到其中最小的元素。

你可以假設數組中不存在重復的元素。

分析:

二分法O(log2n):

如果num[m] < num[r],說明pivot也就是最小的元素在m左邊,極端情況有可能num[m]就是pivot,所以r = m 而不是 r= m -1,如果num[m] > num[r],則pivot在m的右邊,所以l = m + 1

class Solution(object):
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        l = 0
        r = len(nums) - 1
        while l < r:
            m = (l + r) // 2
            if nums[m] < nums[r]:
                r = m
            else:
                l = m + 1
        return nums[l]  

  線性掃描O(n):  

class Solution(object):
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        for i in range(len(nums) - 1):
            if nums[i] > nums[i + 1]:
                return nums[i + 1]
        return nums[0]

  follow up:

數組中存在重復元素,處理方法與上一道題Search in Rotated Sorted Array一樣,對邊緣移動一步,直到邊緣和中間不在相等或者相遇,這就導致了會有不能切去一半的可能。所以最壞情況(比如全部都是一個元素,或者只有一個元素不同於其他元素,而他就在最后一個)就會出現每次移動一步,總共是n步,算法的時間復雜度變成O(n)

class Solution(object):
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        l = 0 
        r = len(nums) - 1
        while l < r:
            m = (l + r) // 2
            if nums[m] < nums[r]:
                r = m
            elif nums[m] > nums[r]:
                l = m + 1
            else:
                r -= 1
        return nums[l]

 

4.Find Peak Element

峰值元素是指其值大於左右相鄰值的元素。

給定一個輸入數組,其中 num[i] ≠ num[i+1],找到峰值元素並返回其索引。

數組可能包含多個峰值,在這種情況下,返回到任何一個峰值所在位置都可以。

你可以想象得到  num[-1] = num[n] = -∞

例如,在數組 [1, 2, 3, 1]中 3 是峰值元素您的函數應該返回索引號2。

你的解決方案應該是對數復雜度的。

分析:

這道題與Find Minimum in Rotated Sorted Array很像,依然可以用二分法或者線性掃描兩種方法解決。

二分法O(log2n):

如果nums[m] > nums[m+1],則說明m的左側肯定存在峰值,因為如果nums[m-1] < nums[m],則說明m是峰值元素,如果說nums[m -1] > nums[m],則m -1的左側還存在峰值,如果一直到m = 0的話,那0這個位置的元素就是峰值。

class Solution(object):
    def findPeakElement(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        l = 0
        r = len(nums) - 1
        while l < r:
            m = (l + r) // 2
            if nums[m] < nums[m + 1]:
                l = m + 1
            else:
                r = m
        return l

線性掃描O(n):

如果當前元素m比前一元素m-1大的話,則繼續向后搜索,如果小的話,說明前一元素m-1即為峰值,因為m-1之前的都比m-1小。

class Solution(object):
    def findPeakElement(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        for i in range(1,len(nums)):
            if nums[i] < nums[i - 1]:
                return i - 1
        return len(nums) - 1

 

 

參考鏈接:

http://blog.csdn.net/linhuanmars/article/details/20588511 


免責聲明!

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



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