<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          ?LeetCode刷題實(shí)戰(zhàn)33:搜索旋轉(zhuǎn)排序數(shù)組

          共 4233字,需瀏覽 9分鐘

           ·

          2020-09-10 15:32

          來源:

          https://www.cnblogs.com/techflow/p/12441002.html

          算法的重要性,我就不多說了吧,想去大廠,就必須要經(jīng)過基礎(chǔ)知識(shí)和業(yè)務(wù)邏輯面試+算法面試。所以,為了提高大家的算法能力,這個(gè)公眾號(hào)后續(xù)每天帶大家做一道算法題,題目就從LeetCode上面選 !

          今天和大家聊的問題叫做搜索旋轉(zhuǎn)排序數(shù)組,我們先來看題面:

          https://leetcode-cn.com/problems/search-in-rotated-sorted-array/

          You are given an integer array nums sorted in ascending order, and an integer target.

          Suppose that nums is rotated at some pivot unknown to you beforehand (i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

          If target is found in the array return its index, otherwise, return -1.

          題意


          假設(shè)按照升序排序的數(shù)組在預(yù)先未知的某個(gè)點(diǎn)上進(jìn)行了旋轉(zhuǎn)。( 例如,數(shù)組 [0,1,2,4,5,6,7] 可能變?yōu)?[4,5,6,7,0,1,2] )。
          搜索一個(gè)給定的目標(biāo)值,如果數(shù)組中存在這個(gè)目標(biāo)值,則返回它的索引,否則返回 -1 。
          你可以假設(shè)數(shù)組中不存在重復(fù)的元素。你的算法時(shí)間復(fù)雜度必須是 O(log n) 級(jí)別。

          樣例


          示例 1:
          輸入: nums = [4,5,6,7,0,1,2], target = 0
          輸出: 4

          示例?2:
          輸入: nums = [4,5,6,7,0,1,2], target = 3
          輸出: -1

          題解

          這題非常明顯,雖然看似限制了時(shí)間復(fù)雜度在提升難度,但其實(shí)是在給大家提示。一個(gè)數(shù)組查找元素,時(shí)間復(fù)雜度還是log(n)這顯然是在暗示大家什么。我想大家作為一個(gè)成熟的程序員,異性的暗示未必讀的懂,但來自出題人的暗示一定能get到【狗頭】。
          顯然,這題需要使用二分法,但是如何使用二分呢,這才是本題的重點(diǎn)。

          兩次二分


          第一種方法非常簡單粗暴,因?yàn)槲覀円呀?jīng)知道要二分查找了,然后我們還知道數(shù)組分成了兩截之后又交換了順序。所以數(shù)組的情況應(yīng)該是這樣的:
          對(duì)不起,我畫得有些抽象,但是精髓傳達(dá)到了。什么意思呢?就是原本升序的數(shù)組分成了兩截之后交換順序,那么現(xiàn)在的數(shù)組應(yīng)該是兩端遞增的序列拼接構(gòu)成的。既然是兩端序列,那么中間顯然有一個(gè)斷點(diǎn)。我們只要找到這個(gè)斷點(diǎn)的位置,就容易了,這個(gè)問題就變成了在兩個(gè)有序的數(shù)組里查找元素了,那就沒有難度了。
          但是這個(gè)斷點(diǎn)怎么找呢?
          顯然不是打開QQ音樂,而是要使用二分法。因?yàn)榍懊嫖覀円呀?jīng)說了,我們整體的復(fù)雜度是log級(jí)的,顯然我們不可能用遍歷來尋找斷點(diǎn)。留給我們的就只有一條路,就是二分。
          設(shè)計(jì)這里的二分需要對(duì)二分法有一定深入的了解,因?yàn)樵谶@個(gè)問題當(dāng)中其實(shí)是不滿足二分法的使用條件的,因?yàn)閿?shù)組被分成了兩個(gè)部分,已經(jīng)不是整體有序了。但是雖然不是整體有序,但仍然是有辦法的。
          我們先來根據(jù)上面的圖來分析一下,很容易發(fā)現(xiàn),斷點(diǎn)的位置就是全局最小值。我們可以找到左側(cè)部分的最大值,或者是右側(cè)部分的最小值,就是斷點(diǎn)的位置。
          我們?cè)賮矸治鲆幌露謺r(shí)候可能出現(xiàn)的情況,一開始的時(shí)候l在最左側(cè),r在最右側(cè),m則是兩側(cè)都有可能。如果m在左側(cè)部分,那么m位置的值一定大于l,否則一定小于l。所以我們通過比較m和l位置元素的大小關(guān)系可以判斷m在左側(cè)還是右側(cè)。
          如果說我們最終的搜索目標(biāo)是尋找左側(cè)部分的最大值,那么當(dāng)m處的值大于l時(shí),則舍棄左側(cè)部分,因?yàn)樽髠?cè)部分已經(jīng)不可能是答案了。同理,如果m處的值小于l,那么應(yīng)該舍棄m右側(cè),因?yàn)閙右側(cè)都在右邊部分,當(dāng)中是不可能有左側(cè)部分的最大值的,通過這種方法我們也可以使用二分查找,只不過條件和我們常用的二分不太一樣。
          def binary_search(arr):
          l, r = 0, len(arr)
          while r - l > 1:
          m = (l + r) >> 1
          if arr[m] > arr[l]:
          l = m
          else:
          r = m
          return l
          找到斷點(diǎn)之后就容易了,就是簡單的無腦二分了。我們來看代碼:
          class Solution:
          def search(self, nums: List[int], target: int) -> int:
          # 找到斷點(diǎn)
          l, r = 0, len(nums)
          if r == 0:
          return -1

          while r - l > 1:
          m = (l + r) >> 1
          if nums[m] > nums[l]:
          l = m
          else:
          r = m

          # 根據(jù)target和第0個(gè)元素比較大小判斷target在左還是右
          if target < nums[0]:
          l, r = r, len(nums)
          else:
          l, r = 0, l+1

          if l == len(nums):
          return -1

          # 二分查找
          while r - l > 1:
          m = (l + r) >> 1
          if nums[m] <= target:
          l = m
          else:
          r = m
          return l if nums[l] == target else -1

          一次二分


          雖然我們?nèi)挛宄亟鉀Q了問題,但是我仍然不夠滿意,總覺得這種做法不夠巧妙,應(yīng)該還能繼續(xù)優(yōu)化。比如說我們能不能不先找斷點(diǎn)的位置直接二分呢?
          仔細(xì)想的話是可以的,尤其是我們已經(jīng)有了上面這種做法的情況下。
          在我們上面查找斷點(diǎn)的時(shí)候,已經(jīng)有了一個(gè)重要的關(guān)鍵信息,就是我們可以通過m和l位置比大小來確定斷點(diǎn)在什么位置。既然我們可以確定斷點(diǎn)的位置,那么我們同樣可以確定target的位置。
          說起來很抽象,我們來看圖:
          這是一種情況,即m的位置在斷點(diǎn)右側(cè),也就是右側(cè)。那么我們通過判斷target和l處的大小關(guān)系可以判斷target可能在哪個(gè)部分。
          如果target > nums[l],那么顯然target在左側(cè),所以我們應(yīng)該讓r=m,即拋棄右部分。
          如果target < nums[l],這個(gè)時(shí)候還不能確定范圍。因?yàn)閙左側(cè)可能還有比m小的元素,所以我們還需要判斷一下target和nums[m]的關(guān)系,來判斷拋棄哪個(gè)部分。如果target < nums[m],那么拋棄右部分,否則拋棄左部分。
          下面來看第二種情況:
          同樣我們判斷target和nums[l]的關(guān)系,如果target > nums[l],那么我們需要繼續(xù)判斷它和m的關(guān)系,來決定拋棄哪個(gè)部分。如果target < nums[l],那么說明我們需要拋棄m左側(cè)的部分。
          情況雖然有點(diǎn)復(fù)雜,但是我們畫個(gè)圖還是很容易理清楚的。一旦理清楚了,我們就可以一次二分搞定全局。
          來看代碼:
          class Solution:
          def search(self, nums: List[int], target: int) -> int:
          if len(nums) == 0:
          return -1

          l, r = 0, len(nums)

          while r - l > 1:
          m = (l + r) >> 1
          # 如果m出現(xiàn)做左側(cè)部分
          if nums[m] >= nums[l]:
          if target >= nums[l]:
          if target < nums[m]:
          r = m
          else:
          l = m
          # 如果target小于nums[l],需要讓l=m+1,因?yàn)閙這個(gè)點(diǎn)也是非法點(diǎn)
          else:
          l = m+1
          else:
          if target >= nums[l]:
          r = m
          else:
          if target >= nums[m]:
          l = m
          else:
          r = m
          return l if l < len(nums) and nums[l] == target else -1
          到這里整個(gè)題目就分析完了,但是比較搞笑的是雖然我們第二種算法看起來牛哄哄,減少了一次二分,但是提交上去之后的結(jié)果反而耗時(shí)要長一些。而且相信大家也感覺到了,這種方法實(shí)現(xiàn)起來要復(fù)雜得多,邊界條件很容易寫錯(cuò)。所以這點(diǎn)告訴我們一個(gè)道理,厲害的方法不一定效果好。
          這道題雖然不難,但是挺有意思,打破了我們一直以來對(duì)于二分法的認(rèn)識(shí),不知道會(huì)不會(huì)給你們帶來腦洞大開的感覺。
          好了,今天的文章就到這里,如果覺得有所收獲,請(qǐng)順手點(diǎn)個(gè)在看或者轉(zhuǎn)發(fā)吧,你們的支持是我最大的動(dòng)力。


          上期推文:


          LeetCode1-20題匯總,速度收藏!
          LeetCode刷題實(shí)戰(zhàn)21:合并兩個(gè)有序鏈表
          LeetCode刷題實(shí)戰(zhàn)23:合并K個(gè)升序鏈表
          LeetCode刷題實(shí)戰(zhàn)24:兩兩交換鏈表中的節(jié)點(diǎn)
          LeetCode刷題實(shí)戰(zhàn)25:K 個(gè)一組翻轉(zhuǎn)鏈表
          LeetCode刷題實(shí)戰(zhàn)26:刪除排序數(shù)組中的重復(fù)項(xiàng)
          LeetCode刷題實(shí)戰(zhàn)27:移除元素
          LeetCode刷題實(shí)戰(zhàn)28:實(shí)現(xiàn) strStr()
          LeetCode刷題實(shí)戰(zhàn)29:兩數(shù)相除
          LeetCode刷題實(shí)戰(zhàn)30:串聯(lián)所有單詞的子串
          LeetCode刷題實(shí)戰(zhàn)31:下一個(gè)排列
          LeetCode刷題實(shí)戰(zhàn)32:最長有效括號(hào)


          瀏覽 34
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品成人va在线观看在线 | 欧美日逼网站 | 91无套 | 骚逼国产| 免费看一级乱伦片 |