<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>

          查找算法常見的五大面試知識點與兩類實戰(zhàn)!

          共 16061字,需瀏覽 33分鐘

           ·

          2020-08-31 01:24

          ↑↑↑關注后"星標"Datawhale
          每日干貨?&?每月組隊學習,不錯過
          ?Datawhale干貨?
          作者:周郴蓮,東北石油大學,Datawhale優(yōu)秀學習者

          前言

          查找(Search),又稱為搜索,指從數(shù)據(jù)表中找出符合特定條件的記錄。如今我們處在信息爆炸的大數(shù)據(jù)時代,如何從海量信息中快速找到需要的信息,這就需要查找技術。如果有什么不懂的或要查詢的,都會上網(wǎng)搜索一下,查找是最常見的應用之一。

          本文解釋了查找的基本概念和查找算法的評價指標,闡述了靜態(tài)查找表的三種具體分類,以及應該如何查找哈希表,手把手教你如何解決查找沖突。最后作者結合Leetcode,帶你刷一刷查找常見題。

          1. 查找的基本概念

          查找也即檢索。首先,簡要說明在查找中涉及的術語。

          文件:由記錄組成的集合,即含有大量數(shù)據(jù)的元素線性組合而成。
          記錄:由若干數(shù)據(jù)項組成的數(shù)據(jù)元素,這些數(shù)據(jù)項也常稱作記錄中的數(shù)據(jù)域,用以表示某個狀態(tài)的物理意義。
          關鍵字:用以區(qū)分文件中記錄的數(shù)據(jù)項的值。若此關鍵字可以惟一地標識一個記錄,則稱此關鍵字為主關鍵字。也就是說,對于不同的記錄,其對應的主關鍵字的值均不相同。若數(shù)據(jù)元素只有一個數(shù)據(jù)項,其關鍵字即為該數(shù)據(jù)元素的值。

          查找是指根據(jù)給定的某個值,確定關鍵字值,查詢確定關鍵字值與給定值相等的記錄在文件中的位置。它是程序設計中一項重要的基本技術。查找的結果有兩種情況:若在文件中找到了待查找的記錄,則稱查找成功,這時可以得到該記錄在文件中的位置,或者得到該記錄中其他的信息;若在文件中沒有找到所需要的記錄,則稱查找不成功或查找失敗,這時,相應的查找算法給出查找失敗的信息,同時也得到記錄插入文件的位置。

          如何進行查找?查找的方法很多,對不同的數(shù)據(jù)結構有不同的查找方法。例如,查電話號碼時,如果電話號碼簿是按用戶的姓名且以筆畫順序編排,則查找的方法是先順序查找待查用戶的所屬類別,然后在此類中再順序查找,直到找尋到用戶的電話號碼為止。又如,查英文單詞時,由于字典是按單詞的字母在字母表中的順序編排的,因此,查找時不需要從字典中第一個單詞開始比較,而只要根據(jù)待查單詞中每個字母在字母表中的位置查找該單詞。在設計相應的查找算法時,就是按以上的步驟進行的。

          應當注意,在計算機中進行查找的方法是根據(jù)文件中的記錄是何種結構組織而確定的,對不同的結構應采用不同的查找方法。

          查找算法的優(yōu)劣對計算機的應用效率影響很大,同樣的一個文件結構,選擇正確的、適合文件組織形式的查找方法可以極大地提高程序的運行速度。查找可分為靜態(tài)查找和動態(tài)查找兩種,在查找過程中不修改查找表的長度和表中內容的方法稱作靜態(tài)查找,反之稱作動態(tài)查找

          2. 查找算法的評價指標

          關鍵字的平均比較次數(shù),也稱平均搜索長度ASL(Average Search Length):

          • n:記錄的個數(shù)
          • pi:查找第i個記錄的概率 ( 通常認為pi =1/n )
          • ci:找到第i個記錄所需的比較次數(shù)

          3. 靜態(tài)查找表

          3.1 順序查找

          1)應用范圍:

          • 順序表或線性鏈表表示的靜態(tài)查找表
          • 表內元素之間無序

          2)順序表的表示:

          typedef struct {
          ElemType *R; //表基址
          int length; //表長
          }SSTable;

          3)順序查找的性能分析:

          • 空間復雜度:一個輔助空間
          • 時間復雜度:
            • 查找成功時的平均查找長度(設表中各記錄查找概率相等):ASLs(n)=(1+2+ … +n)/n =(n+1)/2
            • 查找不成功時的平均查找長度:ASLf =n+1

          4)順序查找算法有特點

          • 算法簡單,對表結構無任何要求(順序和鏈式)。
          • n很大時查找效率較低
          • 改進措施:非等概率查找時,可按照查找概率進行排序

          3.2 折半查找

          1)算法思想:

          設表長為n,low、high和mid分別指向待查元素所在區(qū)間的上界、下界和中點,k為給定值:

          • 初始時,令low=1, high=n, mid=(low+high)/2
          • 讓k與mid指向的記錄比較:若k = R[mid].key,查找成功 若k < R[mid].key,則high=mid-1 若k > R[mid].key,則low=mid+1
          • 重復上述操作,直至low>high時,查找失敗

          例如:


          2)折半查找的性能分析:
          • 查找過程:每次將待查記錄所在區(qū)間縮小一半,比順序查找效率高,時間復雜度O(log2 n)
          • 適用條件:采用順序存儲結構的有序表,不宜用于鏈式結構

          3.3 分塊查找(塊間有序,塊內無序)

          分塊有序,即分成若干子表,要求每個子表中的數(shù)值都比后一塊中數(shù)值小(但子表內部未必有序)。?然后將各子表中的最大關鍵字構成一個索引表,表中還要包含每個子表的起始地址(即頭指針)。

          1)分塊查找過程:
          • 對索引表使用折半查找法(因為索引表是有序表)
          • 確定了待查關鍵字所在的子表后,在子表內采用順序查找法(因為各子表內部是無序表)

          2)分塊查找優(yōu)缺點:

          • 優(yōu)點:插入和刪除比較容易,無需進行大量移動。
          • 缺點:要增加一個索引表的存儲空間并對初始索引表進行排序運算。
          • 適用情況:若線性表既要快速查找又經(jīng)常動態(tài)變化,則可采用分塊查找

          4. 哈希表的查找

          4.1 理論基礎

          1)基本思想:記錄的存儲位置與關鍵字之間存在對應關系

          在這里插入圖片描述
          • 優(yōu)點:查找速度極快O(1),查找效率與元素個數(shù)n無關。

            例如:

          • 查找方法:

            根據(jù)哈希函數(shù)H(k)=k ,查找key=9,則訪問H(9)=9號地址,若內容為9,則成功。若查不到,則返回一個特殊值,如空指針或空記錄。

          2)有關術語

          • 哈希方法(雜湊法)

            • 選取某個函數(shù),依該函數(shù)按關鍵字計算元素的存儲位置,并按此存放;
            • 查找時,由同一個函數(shù)對給定關鍵值k計算地址,將k與地址單元中
          • 元素關鍵碼進行比,確定查找是否成功

            • 哈希函數(shù)(雜湊函數(shù)):哈希方法中使用的轉換函數(shù)
            • 哈希表(雜湊表):按上述思想構造的表
          4.2 沖突解決

          當不同的關鍵碼映射到同一個哈希地址時,即沖突出現(xiàn):key1≠key2,但H(key1)=H(key2)。具有相同函數(shù)值的兩個關鍵字可以成為同義詞。

          1)沖突現(xiàn)象舉例

          2)如何減少沖突

          實際上,沖突是不可能避免的

          3)如何解決沖突:
          • 開放定址法(開地址法)

            其基本思想:有沖突時就去尋找下一個空的哈希地址,只要哈希表足夠大,空的哈希地址總能找到,并將數(shù)據(jù)元素存入

            步驟:

            哈希函數(shù):

            其中:m為哈希表長度,di 為增量序列

            • step1 取數(shù)據(jù)元素的關鍵字key,計算其哈希函數(shù)值(地址)。若該地址對應的存儲空間還沒有被占用,則將該元素存入;否則執(zhí)行step2解決沖突。
            • step2 根據(jù)選擇的沖突處理方法,計算關鍵字key的下一個存儲地址。若下一個存儲地址仍被占用,則繼續(xù)執(zhí)行step2,直到找到能用的存儲地址為止。
          • 鏈地址法

            基本思想:相同哈希地址的記錄鏈成一單鏈表,m個哈希地址就設m個單鏈表,然后用用一個數(shù)組將m個單鏈表的表頭指針存儲起來,形成一個動態(tài)的結構

            步驟:

            優(yōu)點:

            • 非同義詞不會沖突,無“聚集”現(xiàn)象
            • 鏈表上結點空間動態(tài)申請,更適合于表長不確定的情況
            • 取數(shù)據(jù)元素的關鍵字key,計算其哈希函數(shù)值(地址)。若該地址對應的鏈表為空,則將該元素插入此鏈表;

          4.3 函數(shù)構造方法

          1)哈希函數(shù)的構造方法

          2)構造哈希函數(shù)考慮的因素
          • 執(zhí)行速度(即計算哈希函數(shù)所需時間)
          • 關鍵字的長度
          • 哈希表的大小
          • 關鍵字的分布情況
          • 查找頻率

          4.4 哈希表的查找

          • 哈希表的查找效率分析:

            使用平均查找長度ASL來衡量查找算法,ASL取決于:

            α 越大,表中記錄數(shù)越多,說明表裝得越滿,發(fā)生沖突的可能性就越大,查找時比較次數(shù)就越多。ASL與裝填因子α 有關!既不是嚴格的O(1),也不是O(n)。

            • 哈希函數(shù)
            • 處理沖突的方法
            • 哈希表的裝填因子
          • 結論
            • 對哈希表技術具有很好的平均性能,優(yōu)于一些傳統(tǒng)的技術
            • 鏈地址法優(yōu)于開地址法
            • 除留余數(shù)法作哈希函數(shù)優(yōu)于其它類型函數(shù)

          5. 基本數(shù)據(jù)結構

          • 第一類:查找有無–set

            • 元素’a’是否存在,通常用set:集合
            • set只存儲鍵,而不需要對應其相應的值。
            • set中的鍵不允許重復
          • 第二類:查找對應關系(鍵值對應)–dict

            • 元素’a’出現(xiàn)了幾次:dict–>字典
            • dict中的鍵不允許重復
          • 第三類:改變映射關系–map

            • 通過將原有序列的關系映射統(tǒng)一表示為其他

          6. 實戰(zhàn)1(查找表)

          案例1:349 Intersection Of Two Arrays 1

          題目描述

          給定兩個數(shù)組nums,求兩個數(shù)組的公共元素。

          如nums1 = [1,2,2,1],nums2 = [2,2]

          結果為[2]

          結果中每個元素只能出現(xiàn)一次
          出現(xiàn)的順序可以是任意的

          解題思路

          由于每個元素只出現(xiàn)一次,因此不需要關注每個元素出現(xiàn)的次數(shù),用set的數(shù)據(jù)結構就可以了。記錄元素的有和無

          把nums1記錄為set,判斷nums2的元素是否在set中,是的話,就放在一個公共的set中,最后公共的set就是我們要的結果。

          代碼如下:

          class Solution:
          def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
          nums1 = set(nums1)
          return set([i for i in nums2 if i in nums1])

          也可以通過set的內置方法來實現(xiàn),直接求set的交集:

          class Solution:
          def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
          set1 = set(nums1)
          set2 = set(nums2)
          return set2 & set1

          1)解法1(哈希表:時間復雜度:O(m+n),空間復雜度:O(m))

          class Solution:
          def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
          hash_dict = {}
          ans = []
          for i in nums1:
          if hash_dict.get(i) is None:
          hash_dict[i] = 1
          for j in nums2:
          if hash_dict.get(j) is not None:
          ans.append(j)
          hash_dict[j] = None
          return ans

          2)解法2(遍歷,set函數(shù):時間復雜度:O(m+n),空間復雜度:O(m+n))

          class Solution:
          def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
          nums1 = set(nums1)
          nums2 = set(nums2)
          return nums1 & nums2
          案例2:350 Intersection Of Two Arrays 2

          題目描述

          給定兩個數(shù)組nums,求兩個數(shù)組的交集。

          如nums1=[1,2,2,1],nums=[2,2]
          結果為[2,2]

          出現(xiàn)的順序可以是任意的

          解題思路

          元素出現(xiàn)的次數(shù)有用,那么對于存儲次數(shù)就是有意義的,所以選擇數(shù)據(jù)結構時,就應該選擇dict的結構,通過字典的比較來判斷;記錄每個元素的同時要記錄這個元素的頻次

          記錄num1的字典,遍歷nums2,比較nums1的字典的nums的key是否大于零,從而進行判斷。

          代碼如下:

          class Solution:
          def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
          from collections import Counter
          nums1_dict = Counter(nums1)
          res = []
          for num in nums2:
          if nums1_dict[num] > 0:
          # 說明找到了一個元素即在num1也在nums2
          res.append(num)
          nums1_dict[num] -= 1
          return res

          1)解法1(哈希表:時間復雜度:O(n+m),空間復雜度:O(n+m))

          class Solution:
          def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
          n1,n2=collections.Counter(nums1),collections.Counter(nums2)
          res=[]
          for i in n1:
          tmp=min(n1[i],n2[i])
          while tmp>0:
          res.append(i)
          tmp-=1
          return res

          2)解法2(字典)

          class Solution:
          def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
          nums1_map = collections.Counter(nums1)
          nums2_map = collections.Counter(nums2)
          res = []
          for i,j in nums1_map.items():
          if nums2_map.get(i):
          res.extend([i]*min(j,nums2_map.get(i)))
          return res
          案例3:242 Intersection Of Two Arrays 2

          題目描述

          給定兩個字符串 s 和 t ,編寫一個函數(shù)來判斷 t 是否是 s 的字母異位詞。

          示例1:
          輸入: s = "anagram", t = "nagaram"
          輸出: true

          示例 2:
          輸入: s = "rat", t = "car"
          輸出: false

          解題思路

          判斷異位詞即判斷變換位置后的字符串和原來是否相同,那么不僅需要存儲元素,還需要記錄元素的個數(shù)。可以選擇dict的數(shù)據(jù)結構,將字符串s和t都用dict存儲,而后直接比較兩個dict是否相同。

          class Solution:
          def isAnagram(self, s: str, t: str) -> bool:
          from collections import Counter
          s = Counter(s)
          t = Counter(t)
          if s == t:
          return True
          else:
          return False

          1)解法1(哈希表:時間復雜度:O(n),空間復雜度:O(n))

          class Solution:
          def isAnagram(self, s: str, t: str) -> bool:
          if len(s) != len(t):
          return False
          dic = {}
          for i in s:
          if i not in dic:
          dic[i] = 1
          else:
          dic[i] += 1
          for j in t:
          if j not in dic:
          return False
          else:
          dic[j] -= 1
          for val in dic.values():
          if val != 0:
          return False
          return True

          2)解法2(排序法:時間復雜度:O(nlogn),空間復雜度:o(1))

          class Solution:
          def isAnagram(self, s: str, t: str) -> bool:
          if len(s) != len(t): return False
          return True if sorted(s) == sorted(t) else False

          案例4:202 Happy number

          題目描述

          編寫一個算法來判斷一個數(shù)是不是“快樂數(shù)”。

          一個“快樂數(shù)”定義為:對于一個正整數(shù),每一次將該數(shù)替換為它每個位置上的數(shù)字的平方和,然后重復這個過程直到這個數(shù)變?yōu)?1,也可能是無限循環(huán)但始終變不到 1。如果可以變?yōu)?1,那么這個數(shù)就是快樂數(shù)。

          示例:
          輸入: 19
          輸出: true

          解釋:
          1^2 + 9^2 = 82
          8^2 + 2^2 = 68
          6^2 + 8^2 = 100
          1^2 + 0^2 + 0^2 = 1

          解題思路

          這道題目思路很明顯,當n不等于1時就循環(huán),每次循環(huán)時,將其最后一位到第一位的數(shù)依次平方求和,比較求和是否為1。

          難點在于,什么時候跳出循環(huán)?

          開始筆者的思路是,循環(huán)個100次,還沒得出結果就false,但是小學在算無限循環(huán)小數(shù)時有一個特征,就是當除的數(shù)中,和之前歷史的得到的數(shù)有重合時,這時就是無限循環(huán)小數(shù)。

          那么這里也可以按此判斷,因為只需要判斷有或無,不需要記錄次數(shù),故用set的數(shù)據(jù)結構。每次對求和的數(shù)進行append,當新一次求和的值存在于set中時,就return false。

          代碼如下:

          class Solution:
          def isHappy(self, n: int) -> bool:
          already = set()
          while n != 1:
          sum = 0
          while n > 0:
          # 取n的最后一位數(shù)
          tmp = n % 10
          sum += tmp ** 2
          # 將n的最后一位截掉
          n //= 10
          # 如果求的和在過程中出現(xiàn)過
          if sum in already:
          return False
          else:
          already.add(sum)
          n = sum
          return True

          tips

          #一般對多位數(shù)計算的套路是:

          #循環(huán)從后向前取位數(shù)
          while n >0 :
          #取最后一位:
          tmp = n % 10
          #再截掉最后一位:
          n = n // 10

          1)解法1(快慢指針:時間復雜度:O(log n),空間復雜度:O(1))

          class Solution:
          def isHappy(self, n: int) -> bool:
          f = lambda n: reduce(lambda x, y: x + y ** 2, (int(i) for i in str(n)), 0)
          slow, fast = n, f(n)
          while slow != fast:
          slow = f(slow)
          fast = f(f(fast))
          if slow == 1 or fast == 1:
          return True
          return slow == 1

          2)解法2(哈希表:時間復雜度:O(log n),空間復雜度:O(log n))

          class Solution:
          def isHappy(self, n: int) -> bool:
          already = set()
          while n != 1:
          if n in already:
          return False
          already.add(n)
          n = sum([int(x) * int(x) for x in str(n)])
          return True

          案例5:290 Word Pattern

          題目描述

          給出一個模式(pattern)以及一個字符串,判斷這個字符串是否符合模式。

          示例1:
          輸入: pattern = "abba",
          str = "dog cat cat dog"
          輸出: true

          示例 2:
          輸入:pattern = "abba",
          str = "dog cat cat fish"
          輸出: false

          示例 3:
          輸入: pattern = "aaaa", str = "dog cat cat dog"
          輸出: false

          示例 4:
          輸入: pattern = "abba", str = "dog dog dog dog"
          輸出: false

          解題思路

          抓住變與不變,筆者開始的思路是選擇了dict的數(shù)據(jù)結構,比較count值和dict對應的keys的個數(shù)是否相同,但是這樣無法判斷順序的關系,如測試用例:‘aba’,‘cat cat dog’。

          那么如何能既考慮順序,也考慮鍵值對應的關系呢?

          抓住變與不變,變的是鍵,但是不變的是各個字典中,對應的相同index下的值,如dict1[index] = dict2[index],那么我們可以創(chuàng)建兩個新的字典,遍歷index對兩個新的字典賦值,并比較value。

          還有一個思路比較巧妙,既然不同,那么可以考慮怎么讓它們相同,將原來的dict通過map映射為相同的key,再比較相同key的dict是否相同。

          代碼實現(xiàn)如下:

          class Solution:
          def wordPattern(self,pattern, str):
          str = str.split()
          return list(map(pattern.index,pattern)) == list(map(str.index,str))

          tips

          • 因為str是字符串,不是由單個字符組成,所以開始需要根據(jù)空格拆成字符list:
            str = str.split()
          • 通過map將字典映射為index的list:
            map(pattern.index, pattern)
          • map是通過hash存儲的,不能直接進行比較,需要轉換為list比較list

          1)解法1(哈希表:時間復雜度: O(n),空間復雜度: O(n))

          class Solution:
          def wordPattern(self, pattern: str, str: str) -> bool:
          s = str.split()
          if len(pattern) != len(s):
          return False
          dict1, dict2 = {}, {}
          for i in range(len(pattern)):
          if pattern[i] not in dict1 and s[i] not in dict2:
          dict1[pattern[i]] = s[i]
          dict2[s[i]] = pattern[i]
          elif pattern[i] in dict1:
          if dict1[pattern[i]] != s[i]:
          return False
          elif s[i] in dict2:
          if dict2[s[i]] != pattern[i]:
          return False
          return True

          案例6:205 Isomorphic Strings

          題目描述

          給定兩個字符串 s 和 t,判斷它們是否是同構的。

          如果 s 中的字符可以被替換得到 t ,那么這兩個字符串是同構的。

          所有出現(xiàn)的字符都必須用另一個字符替換,同時保留字符的順序。兩個字符不能映射到同一個字符上,但字符可以映射自己本身。

          示例 1:
          輸入: s = "egg", t = "add"
          輸出: true

          示例 2:
          輸入: s = "foo", t = "bar"
          輸出: false

          示例 3:
          輸入: s = "paper", t = "title"
          輸出: true

          解題思路

          思路與上題一致,可以考慮通過建兩個dict,比較怎樣不同,也可以將不同轉化為相同。

          直接用上題的套路代碼:

          class Solution:
          def isIsomorphic(self, s: str, t: str) -> bool:
          return list(map(s.index,s)) == list(map(t.index,t))

          1)解法1(哈希表:時間復雜度: O(n),空間復雜度: O(1))

          class Solution:
          def isIsomorphic(self, s: str, t: str) -> bool:
          record = {}
          if len(set(s))!=len(set(t)):
          return False
          for ss, tt in zip(s, t):
          if ss not in record:
          record[ss] = tt
          else:
          if record[ss] != tt:
          return False
          return True

          2)解法2(字典)

          class Solution:
          def isIsomorphic(self, s: str, t: str) -> bool:
          map1 = {}
          map2 = {}
          for i in range(len(s)):
          if s[i] in map1 and t[i] != map1[s[i]]:
          return False
          if t[i] in map2 and s[i] != map2[t[i]]:
          return False
          map1[s[i]] = t[i]
          map2[t[i]] = s[i]
          return True

          案例7:451 Sort Characters By Frequency

          題目描述

          給定一個字符串,請將字符串里的字符按照出現(xiàn)的頻率降序排列。

          示例 1:
          輸入:
          "tree"
          輸出:
          "eert"

          示例 2:
          輸入:
          "cccaaa"
          輸出:
          "cccaaa"

          示例 3:
          輸入:
          "Aabb"
          輸出:
          "bbAa"

          解題思路

          對于相同頻次的字母,順序任意,需要考慮大小寫,返回的是字符串。

          使用字典統(tǒng)計頻率,對字典的value進行排序,最終根據(jù)key的字符串乘上value次數(shù),組合在一起輸出。

          class Solution:
          def frequencySort(self, s: str) -> str:
          from collections import Counter
          s_dict = Counter(s)
          # sorted返回的是列表元組
          s = sorted(s_dict.items(), key=lambda item:item[1], reverse = True)
          # 因為返回的是字符串
          res = ''
          for key, value in s:
          res += key * value
          return res

          tips

          • 通過sorted的方法進行value排序,對字典排序后無法直接按照字典進行返回,返回的為列表元組:
          # 對value值由大到小排序
          s = sorted(s_dict.items(), key=lambda item:item[1], reverse = True)

          # 對key由小到大排序
          s = sorted(s_dict.items(), key=lambda item:item[0])
          • 輸出為字符串的情況下,可以由字符串直接進行拼接:
          # 由key和value相乘進行拼接
          's' * 5 + 'd'*2

          1)解法1(字典:時間復雜度: O(nlogn),空間復雜度: O(n))

          class Solution:
          def frequencySort(self, s: str) -> str:
          cnt = defaultdict(int)
          for i in s:
          cnt[i] += 1

          ans = ""
          for k, v in sorted(cnt.items(), key=lambda x: -x[1]):
          ans += k * v
          return ans
          2)解法2(Counter)
          class Solution:
          def frequencySort(self, s: str) -> str:
          return "".join(k * v for k, v in collections.Counter(s).most_common())

          7. 實戰(zhàn)2(二分查找)

          7.1 理解

          查找在算法題中是很常見的,但是怎么最大化查找的效率和寫出bugfree的代碼才是難的部分。一般查找方法有順序查找、二分查找和雙指針,推薦一開始可以直接用順序查找,如果遇到TLE的情況再考慮剩下的兩種,畢竟AC是最重要的。

          一般二分查找的對象是有序或者由有序部分變化的(可能暫時理解不了,看例題即可),但還存在一種可以運用的地方是按值二分查找,之后會介紹。

          7.2 代碼模板

          總體來說二分查找是比較簡單的算法,網(wǎng)上看到的寫法也很多,掌握一種就可以了。以下是我的寫法,參考C++標準庫里的寫法。這種寫法比較好的點在于:

          • 即使區(qū)間為空、答案不存在、有重復元素、搜索開/閉區(qū)間的上/下界也同樣適用
          • ±1 的位置調整只出現(xiàn)了一次,而且最后返回lo還是hi都是對的,無需糾結
          class Solution:
          def firstBadVersion(self, arr):
          # 第一點
          lo, hi = 0, len(arr)-1
          while lo < hi:
          # 第二點
          mid = (lo+hi) // 2
          # 第三點
          if f(x):
          lo = mid + 1
          else:
          hi = mid
          return lo

          解釋:

          • 第一點:lo和hi分別對應搜索的上界和下界,但不一定為0和arr最后一個元素的下標。
          • 第二點:因為Python沒有溢出,int型不夠了會自動改成long int型,所以無需擔心。如果再苛求一點,可以把這一行改成
          mid = lo + (hi-lo) // 2
          # 之所以 //2 這部分不用位運算 >> 1 是因為會自動優(yōu)化,效率不會提升
          • 第三點:比較重要的就是這個f(x),在帶入模板的情況下,寫對函數(shù)就完了。
            那么我們一步一步地揭開二分查找的神秘面紗,首先來一道簡單的題。

          案例1:35. Search Insert Position

          題目描述

          給定排序數(shù)組和目標值,如果找到目標,則返回索引。如果不是,則返回按順序插入索引的位置的索引。您可以假設數(shù)組中沒有重復項。

          Example 1:
          Input: [1,3,5,6], 5
          Output: 2

          Example 2:
          Input: [1,3,5,6], 2
          Output: 1

          Example 3:
          Input: [1,3,5,6], 7
          Output: 4

          Example 4:
          Input: [1,3,5,6], 0
          Output: 0

          解題思路

          這里要注意的點是 high 要設置為 len(nums) 的原因是像第三個例子會超出數(shù)組的最大值,所以要讓 lo 能到 這個下標。

          class Solution:
          def searchInsert(self, nums: List[int], target: int) -> int:
          lo, hi = 0, len(nums)
          while lo < hi:
          mid = (lo + hi) // 2
          if nums[mid] < target:
          lo = mid + 1
          else:
          hi = mid
          return lo

          1)解法1(暴力破解:時間復雜度:O(N),空間復雜度:O(1))

          class Solution:
          def searchInsert(self, nums: List[int], target: int) -> int:
          n = len(nums)
          for i in range(-1, n - 1):
          if nums[i + 1] >= target:
          return i + 1
          return n

          2)解法2(二分法:時間復雜度:O(logn),空間復雜度:O(1))

          class Solution:
          def searchInsert(self, nums: List[int], target: int) -> int:
          size = len(nums)
          if size==0:
          return 0
          if nums[size-1] return size
          left, right = 0, size-1
          while left < right:
          mid = (left + right) // 2
          if nums[mid] < target:
          left = mid + 1
          else:
          right = mid
          return left
          案例2:540. Single Element in a Sorted Array

          題目描述

          您將獲得一個僅由整數(shù)組成的排序數(shù)組,其中每個元素精確出現(xiàn)兩次,但一個元素僅出現(xiàn)一次。找到只出現(xiàn)一次的單個元素。

          Example
          Example 1:

          Input: [1,1,2,3,3,4,4,8,8]
          Output: 2

          Example 2:

          Input: [3,3,7,7,10,11,11]
          Output: 10

          解題思路

          異或的巧妙應用!如果mid是偶數(shù),那么和1異或的話,那么得到的是mid+1,如果mid是奇數(shù),得到的是mid-1。如果相等的話,那么唯一的元素還在這之后,往后找就可以了。

          class Solution:
          def singleNonDuplicate(self, nums):
          lo, hi = 0, len(nums) - 1
          while lo < hi:
          mid = (lo + hi) // 2
          if nums[mid] == nums[mid ^ 1]:
          lo = mid + 1
          else:
          hi = mid
          return nums[lo]

          是不是還挺簡單哈哈,那我們來道HARD難度的題!

          1)解法1(二分法:時間復雜度:O(logn),空間復雜度:O(n))

          class Solution(object):
          def singleNonDuplicate(self, nums):
          l = 0
          r = len(nums) - 1
          while l < r:
          mid = l + (r-l)//2
          if mid % 2 == 1:
          mid -= 1
          if nums[mid] == nums[mid+1]:
          l = mid + 2
          else:
          r = mid
          return nums[l]

          案例3:410. Split Array Largest Sum

          題目描述

          給定一個由非負整數(shù)和整數(shù)m組成的數(shù)組,您可以將該數(shù)組拆分為m個非空連續(xù)子數(shù)組。編寫算法以最小化這m個子數(shù)組中的最大和。

          Example
          Input:
          nums = [7,2,5,10,8]
          m = 2

          Output:
          18

          Explanation:
          There are four ways to split nums into two subarrays.
          The best way is to split it into [7,2,5] and [10,8],
          where the largest sum among the two subarrays is only 18.

          解題思路

          • 這其實就是二分查找里的按值二分了,可以看出這里的元素就無序了。但是我們的目標是找到一個合適的最小和,換個角度理解我們要找的值在最小值max(nums)和sum(nums)內,而這兩個值中間是連續(xù)的。是不是有點難理解,那么看代碼吧
          • 輔助函數(shù)的作用是判斷當前的“最小和”的情況下,區(qū)間數(shù)是多少,來和m判斷
          • 這里的下界是數(shù)組的最大值是因為如果比最大值小那么一個區(qū)間就裝不下,數(shù)組的上界是數(shù)組和因為區(qū)間最少是一個,沒必要擴大搜索的范圍
          class Solution:
          def splitArray(self, nums: List[int], m: int) -> int:

          def helper(mid):
          res = tmp = 0
          for num in nums:
          if tmp + num <= mid:
          tmp += num
          else:
          res += 1
          tmp = num
          return res + 1

          lo, hi = max(nums), sum(nums)
          while lo < hi:
          mid = (lo + hi) // 2
          if helper(mid) > m:
          lo = mid + 1
          else:
          hi = mid
          return lo

          1)解法1(二分法:時間復雜度:O(n*log(sum(nums)-max(nums))),空間復雜度:O(1))

          class Solution:
          def splitArray(self, nums: List[int], m: int) -> int:
          lo, hi = 0, 0
          for num in nums:
          lo = max(lo, num)
          hi += num
          while lo < hi:
          mid = lo + ((hi - lo) >> 1)
          if self._count(nums, mid) > m:
          lo = mid + 1
          else:
          hi = mid
          return lo

          def _count(self, nums, val):
          count = 1
          _sum = 0
          for n in nums:
          _sum += n
          if _sum > val:
          count += 1
          _sum = n
          return count

          2)解法2(動態(tài)規(guī)劃:時間復雜度O(m_len(nums)),空間復雜度O(m_len(nums)))

          class Solution:
          def splitArray(self, nums: List[int], m: int) -> int:
          from functools import lru_cache
          n = len(nums)

          def dfs(i, k):
          if k == 1:
          return sum(nums[i:])
          res = float("inf")
          cur = 0
          for j in range(i, n):
          cur += nums[j]
          # 關鍵
          if res <= cur:
          break
          res = min(res, max(cur, dfs(j + 1, k - 1)))
          return res

          return dfs(0, m)

          【參考資料】

          1. 編程實踐(LeetCode 分類練習)
          2. 力扣(LeetCode)平臺
          “整理不易,三連
          瀏覽 77
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  久久爱成人电影 | 欧美三级网址 | 国产老熟女一区二区三区 | 久草com | 国产欧美自拍 |