LeetCode 81,在不知足二分的數組內使用二分法 II

本文始發於我的公衆號:TechFlow,原創不易,求個關注web


今天是LeetCode專題第50篇文章,咱們來聊聊LeetCode中的81題Search in Rotated Sorted ArrayII。算法

它的官方難度是Medium,點贊1251,反對470,經過率32.8%。從經過率上來看,這題屬於Medium難度當中偏難一些的題目,也的確如此,稍稍有些考驗思惟。數組

題意

假設咱們有一個含有重複元素的有序數組,咱們隨意選擇一個位置將它分紅兩半,而後將這兩個部分調換順序拼接成一個新的數組。如今給定一個target,要求返回一個bool結果,代表target是否在數組當中編輯器

樣例

Input: nums = [2,5,6,0,0,1,2], target = 0
Output: true
Input: nums = [2,5,6,0,0,1,2], target = 3
Output: false

若是是你按照順序刷LeetCode或者是本專題的話,你會發現咱們在以前作過一道很是類似的題目。它就是LeetCode的33題,Search in Rotated Sorted ArrayI。不過不一樣的是,在33題的題意當中,明確代表了數組當中的元素是不包含重複元素的,除此以外,這兩題的題意徹底同樣。url

LeetCode 33,在不知足二分的數組內使用二分的方法spa

這麼一點小小的差異會帶來解法的變化嗎?3d

題解

答案固然是確定的,否則出題人能夠退休了。code

問題是,問題出在哪裏呢?blog

咱們先不着急,先來回憶一下33題中的作法。咱們當時使用了一個最簡單的笨辦法,就是先經過二分法找到數組截斷的位置。而後再經過截斷的位置還原出原數組的狀況,根據咱們target的大小,找到它可能存在的位置。遞歸

可是在當前這個問題當中,這個思路走不通了。走不通的緣由也很簡單,就是由於重複元素的存在。

舉個例子:[1, 3, 1, 1, 1, 1, 1, 1]

當咱們進行二分查找的時候,發現mid是1和left的1相等,咱們根本沒法判斷截斷點究竟在mid的左側仍是右側,二分查找也就無從談起了。

咱們固然能夠退一步採用遍歷的方法去尋找切分點,可是既然如此,咱們爲何不直接去尋找答案呢?反正都已是O(n)的複雜度了。因此這是行不通的,咱們想要使得複雜度維持在就必需要尋找其餘的路數。

思路和解法不少時候不是憑空來的,須要咱們對問題進行深刻的分析。在這個問題當中,咱們的問題是明確而且簡單的。就是一個調換了部分順序的有序數組,只是咱們不肯定的是調換的部分究竟有多長。因爲咱們最終但願經過二分法來尋找答案,因此咱們能夠根據調換的元素是否過半想出兩種狀況來。

我把這兩種狀況用圖展現出來:

也就是說咱們的分割點可能在數組的前半段也可能在後半段,對於這兩種狀況咱們的處理方法是不一樣的。

咱們先看第一種狀況,數組的前半段是有序的,後半段存在截斷。若是target的範圍在前半段當中,咱們能夠拋棄掉後半段,直接在前半段中進行二分。不然,咱們須要捨棄前半段,在後半段當中重複這個過程。咱們能夠把後半段當作是一個全新的問題,也同樣能夠分紅兩種狀況,相似於遞歸同樣的往下執行便可。

再來看第二種狀況,第二種狀況的後半段和第一種狀況的前半段是同樣的,都是有序的元素,咱們直接二分便可。它的前半段和第一種狀況的後半段是同樣的,咱們無法判斷,須要繼續二分。

也就是說,咱們只能在有序的數組進行二分,若是當前數組存在分段,不是總體有序的,那咱們就對它進行拆分。拆分以後總能找到有序的部分,若是還找不到就繼續拆分。由於分段點只有一個,因此不論當前的數組什麼樣,拆分一次以後,必然至少能夠找到一段是有序的

想明白這點以後就簡單了,看起來很像是遞歸,但實際上它的本質仍然是二分。代碼並不難寫,可是還有一個問題沒解決,就是當nums[m] = nums[l]的時候,咱們如何判斷是哪種狀況呢?

答案是無法判斷,兩種狀況都有可能,對於這種狀況也沒有很好的辦法,我想出來的辦法是能夠將l向右移動一位,至關於拋棄了一個最左側的數。咱們把這些思路總結總結,代碼也就出來了:

class Solution:
    def search(self, nums: List[int], target: int) -> bool:
        l, r = 0, len(nums)-1
        while l <= r:
            m = (l + r) >> 1
            if nums[m] == target:
                return True
            
            if nums[l] == nums[m]:
                l += 1
                continue
                
            if nums[l] < nums[m]:
                if nums[l] <= target < nums[m]:
                    r = m - 1
                else:
                    l = m + 1
            else:
                if nums[m] < target <= nums[r]:
                    l = m + 1
                else:
                    r = m - 1
        return False

總結

到這裏,咱們關於這道題的題解就結束了。在問題的最後,出題人給咱們留了一個問題,和33題比起來,這題的解法的時間複雜度會有變化嗎

表面上看咱們同樣用到了二分,因此一樣是log級的複雜度,問題的複雜度並無變化。但實際上並非這樣的,咱們來看一種最壞的狀況,假設數組當中全部的值所有相等。這個時候二分就不起效果了,最終會退化成O(n)的線性枚舉,這樣又變成了O(n)的複雜度。固然,在大部分狀況下,這並不會發生。因此是算法的最壞複雜度退化成了O(n),平均複雜度依然是O(logN)。

若是喜歡本文,能夠的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

本文使用 mdnice 排版

相關文章
相關標籤/搜索