分治策略

分治策略

本文包括

  1. 分治的基本概念
  2. 二分查找
  3. 快速排序
  4. 歸并排序
  5. 找出偽幣
  6. 棋盤覆蓋
  7. 最大子數(shù)組

源碼鏈接:https://github.com/edisonleolhl/DataStructure-Algorithm/tree/master/Divede-and-conquer

分治的基本概念

  • 在排序算法中有種算法叫做歸并排序,它采用了分治策略,在策略中,我們遞歸地求解一個(gè)問題,在每層遞歸中應(yīng)用如下三個(gè)步驟:

    • 分解(Divide)步驟將問題劃分為一些子問題,子問題的形式與原問題一樣,只是規(guī)模更小。
    • 解決(Conquer)步驟遞歸地求解出子問題,如果子問題的規(guī)模足夠小,則停止遞歸,直接求解。
    • 合并(Combine)步驟將子問題的解組合成原問題的解。
  • 當(dāng)子問題足夠大時(shí),需要遞歸求解,我們稱之為遞歸情況(recursive case)。

  • 當(dāng)子問題足夠小,不需要遞歸求解時(shí),我們稱之為“觸底”,進(jìn)入了基本情況(base case)。

  • 很多時(shí)候,問題看上去并不是一目了然的,能否用分治策略,可以取決于問題是否滿足以下四條特征:

    • 該問題的規(guī)模縮小到一定的程度就可以容易地解決;
    • 該問題可以分解為若干個(gè)規(guī)模較小的相同問題,即該問題具有最優(yōu)子結(jié)構(gòu)性質(zhì);
    • 利用該問題分解出的子問題的解可以合并為該問題的解;
    • 該問題所分解出的各個(gè)子問題是相互獨(dú)立的,即子問題之間不包含公共的子問題。
  • 遞歸式(recurrence)就是一個(gè)等式或者不等式,比如歸并排序的最壞情況運(yùn)行時(shí)間:

           { θ(1)             若 n=1
      T(n)=|
           { 2T(n/2)+θ(n)     若 n>1
    

    求解可得 T(n)=θ(nlogn)

二分查找

  • 我們先看個(gè)簡(jiǎn)單的例子,二分查找,假設(shè)我們想要查找 x 是否存在于已排序列 a[] 中。

百度百科:如果線性表里只有一個(gè)元素,則只要比較這個(gè)元素和x就可以確定x是否在線性表中。因此這個(gè)問題滿足分治法的第一個(gè)適用條件;同時(shí)我們注意到對(duì)于排好序的線性表L有以下性質(zhì):比較x和L中任意一個(gè)元素L[i],若x=L[i],則x在L中的位置就是i;如果x<L[i],由于L是遞增排序的,因此假如x在L中的話,x必然排在L[i]的前面,所以我們只要在L[i]的前面查找x即可;如果x>L[i],同理我們只要在L[i]的后面查找x即可。無(wú)論是在L[i]的前面還是后面查找x,其方法都和在L中查找x一樣,只不過是線性表的規(guī)模縮小了。這就說(shuō)明了此問題滿足分治法的第二個(gè)和第三個(gè)適用條件。很顯然此問題分解出的子問題相互獨(dú)立,即在L[i]的前面或后面查找x是獨(dú)立的子問題,因此滿足分治法的第四個(gè)適用條件。

  • 二分查找的基本思想是將n個(gè)元素分成大致相等的兩部分,取a[n/2]與x做比較。

    • 如果x=a[n/2],則找到x,算法中止;
    • 如果x<a[n/2],則只需要在 a[] 的左半部分繼續(xù)搜索 x;
    • 如果x>a[n/2],則只需要在 a[] 的右半部分繼續(xù)搜索 x。
  • 容易理解,時(shí)間復(fù)雜度無(wú)非就是迭代的次數(shù),O(logn)

  • 利用 while 循環(huán)的偽代碼:

      BinarySearch(max,min,des)
          mid-<(max+min)/2
          while(min<=max)
              mid=(min+max)/2
              if mid=des then
                  return mid
              elseif mid >des then
                  max=mid-1
              else
                  min=mid+1
          return
    
  • 利用迭代的 Python 代碼:

      def binary_search(A, x, low, high):
          if low == high:
              return -1
          mid = (low + high) // 2
          if A[mid] == x:
              return mid
          result_left = binary_search(A, x, low, mid)
          print("left", result_left)
          result_right = binary_search(A, x, mid+1, high)
          print("right", result_right)
          if result_left != -1:
              return result_left
          elif result_right != -1:
              return result_right
          else:
              return -1
    
      A = list(range(10))
      print(binary_search(A, 3, 0, len(A)-1))
    
  • 輸出

      left -1
      right -1
      left -1
      right -1
      left -1
      right 3
      left 3
      left -1
      right -1
      left -1
      right -1
      left -1
      left -1
      right -1
      right -1
      right -1
      3
    

快速排序

  • 快速排序的基本思想是基于分治法的

  • 對(duì)于輸入的子序列L[p..r],如果規(guī)模足夠小則直接進(jìn)行排序,否則分三步處理:

    • 分解(Divide):將輸入的序列L[p..r]劃分成兩個(gè)非空子序列L[p..q]和L[q+1..r],使L[p..q]中任一元素的值不大于L[q+1..r]中任一元素的值。
    • 遞歸求解(Conquer):通過遞歸調(diào)用快速排序算法分別對(duì)L[p..q]和L[q+1..r]進(jìn)行排序。
    • 合并(Merge):由于對(duì)分解出的兩個(gè)子序列的排序是就地進(jìn)行的,所以在L[p..q]和L[q+1..r]都排好序后不需要執(zhí)行任何計(jì)算L[p..r]就已排好序。
  • 這個(gè)解決流程是符合分治法的基本步驟的,因此,快速排序法是分治法的經(jīng)典應(yīng)用實(shí)例之一。

  • 詳情見之前寫的排序算法:http://www.lxweimin.com/p/7cb29ad6d0f7

歸并排序

  • 也是分治策略的典型應(yīng)用,具體見排序算法,文章了鏈接同上。

x^n

  • 輸入 x 與 n,求 x^n

  • 最樸素的方法就是把 x 連乘 x,這樣需要時(shí)間復(fù)雜度為 Θ(n)

  • 但是如果把計(jì)算式分解成奇數(shù)和偶數(shù)的情況,時(shí)間復(fù)雜度降為 Θ(logn)

      x^n = x^(n/2) × x^(n/2)  當(dāng) n 為偶數(shù)
            x^(n-1/2) × x^(n-1/2) ×  當(dāng) n 為奇數(shù)
    

找出偽幣

  • 給你一個(gè)裝有16個(gè)硬幣的袋子。16個(gè)硬幣中有一個(gè)是偽造的,并且那個(gè)偽造的硬幣比真的硬幣要輕一些。你的任務(wù)是找出這個(gè)偽造的硬幣。為了幫助你完成這一任務(wù),將提供一臺(tái)可用來(lái)比較兩組硬幣重量的儀器,利用這臺(tái)儀器,可以知道兩組硬幣的重量是否相同。

  • 可以想到,一個(gè)很簡(jiǎn)單的方法就是暴力枚舉法:

    • 比較硬幣1與硬幣2的重量。假如硬幣1比硬幣2輕,則硬幣1是偽造的;假如硬幣2比硬幣1輕,則硬幣2是偽造的。這樣就完成了任務(wù)。

    • 假如兩硬幣重量相等,則比較硬幣3和硬幣4。同樣,假如有一個(gè)硬幣輕一些,則尋找偽幣的任務(wù)完成。假如兩硬幣重量相等,則繼續(xù)比較硬幣5和硬幣6。按照這種方式,可以最多通過8次比較來(lái)判斷偽幣的存在并能夠找出這一偽幣。

  • 另外一種方法就是利用分而治之的方法:

    • 假如把16硬幣的例子看成一個(gè)大的問題。

    • 第一步,把這一問題分成兩個(gè)小問題。隨機(jī)選擇8個(gè)硬幣作為第一組稱為A組,剩下的8個(gè)硬幣作為第二組稱為B組。這樣,就把16個(gè)硬幣的問題分成兩個(gè)8硬幣的問題來(lái)解決。

    • 第二步,判斷A和B組中是否有偽幣。可以利用儀器來(lái)比較A組硬幣和B組硬幣的重量。假如兩組硬幣重量相等,則可以判斷偽幣不存在,此時(shí)直接結(jié)束算法。假如兩組硬幣重量不相等,則存在偽幣,并且可以判斷它位于較輕的那一組硬幣中,然后繼續(xù)把較輕組的硬幣繼續(xù)劃分為兩組,放在儀器中比較重量。一直這樣迭代下去,直至兩枚硬幣比較,較輕的那枚就是偽幣。

  • 當(dāng)然,如果硬幣數(shù)量為奇數(shù)的話就不能這么簡(jiǎn)單了,但是這里只是為了體現(xiàn)分治的思想,所以先用偶數(shù)說(shuō)明概念。

棋盤覆蓋

  • 問題描述:在一個(gè) 2k×2k 個(gè)方格組成的棋盤中,恰有一個(gè)方格與其他方格不同,稱該方格為一特殊方格,且稱該棋盤為一特殊棋盤。在棋盤覆蓋問題中,要用圖示的4種不同形態(tài)的L型骨牌覆蓋給定的特殊棋盤上除特殊方格以外的所有方格,且任何2個(gè)L型骨牌不得重疊覆蓋。

  • 分析:

    • 當(dāng) k>0 時(shí),將 2k×2k 棋盤分割為 4 個(gè) 2(k-1)×2(k-1) 的 子棋盤,如下圖 a 所示。

    • 特殊方格必位于4個(gè)較小子棋盤之一中,其余3個(gè)子棋盤中無(wú)特殊方格。為了將這3個(gè)無(wú)特殊方格的子棋盤轉(zhuǎn)化為特殊棋盤,可以用一個(gè)L型骨牌覆蓋這3個(gè)較小棋盤的會(huì)合處,如 (b)所示,從而將原問題轉(zhuǎn)化為4個(gè)較小規(guī)模的棋盤覆蓋問題。遞歸地使用這種分割,直至棋盤簡(jiǎn)化為棋盤1×1。

  • 實(shí)現(xiàn):每次都對(duì)分割后的四個(gè)小方塊進(jìn)行判斷,判斷特殊方格是否在里面。這里的判斷的方法是每次先記錄下整個(gè)大方塊的左上角(top left coner)方格的行列坐標(biāo),然后再與特殊方格坐標(biāo)進(jìn)行比較,就可以知道特殊方格是否在該塊中。如果特殊方塊在里面,這直接遞歸下去求即可,如果不在,則根據(jù)分割的四個(gè)方塊的不同位置,把右下角、左下角、右上角或者左上角的方格標(biāo)記為特殊方塊,然后繼續(xù)遞歸。這樣我們就按照要求填充了整個(gè)棋盤。

最大子數(shù)組

  • 題目描述:輸入一個(gè)整形數(shù)組,數(shù)組里有正數(shù)也有負(fù)數(shù)。數(shù)組中連續(xù)的一個(gè)或多個(gè)整數(shù)組成一個(gè)子數(shù)組,每個(gè)子數(shù)組都有一個(gè)和。設(shè)計(jì)一種算法求出輸入數(shù)組的最大子數(shù)組以及對(duì)應(yīng)的子數(shù)組和是多少。

  • 例如輸入的數(shù)組為

      13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7
    

    那么最大的子數(shù)組為

      18,20,-7,12
    

    因此輸出該子數(shù)組

      43, 7, 10
    

解法一:暴力求解

  • 兩個(gè) for 循環(huán),時(shí)間復(fù)雜度 O(n^2)

  • 思路:

  • 代碼:

    def di_cal_wrong(A):
        max_sub_sum = -float('inf')  # init
        for i in range(len(A)):
            for j in range(i+1, len(A)):
                if sum(A[i:j+1]) > max_sub_sum:
                    max_sub_sum = sum(A[i:j+1])
                    low = i
                    high = j
        return(max_sub_sum, low, high)
    
    A = [13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7]
    print(di_cal(A))
    
  • 輸出:

    (43, 7, 10)
    
  • 思考:上述代碼有錯(cuò)嗎?

  • Python 列表的切片操作,如果放在類似于 Java 、C 之類的語(yǔ)言中,想要實(shí)現(xiàn)的話得通過 for 循環(huán)然后依次累加,這時(shí)豈不是3個(gè) for 循環(huán)嵌套了?

  • 更正:每當(dāng) j 增加1后,令 sum = sum + A[J],然后再比較 sum 是否小于 max_sub_sum,這樣就實(shí)現(xiàn)了兩次 for 循環(huán)嵌套。

  • 更正后的代碼:

    def di_cal(A):
        sum = A[0]
        max_sub_sum = -float('inf')  # init
        for i in range(len(A)):
            sum = A[i]
            for j in range(i+1, len(A)):
                sum += A[j]
                if sum > max_sub_sum:
                    max_sub_sum = sum
                    low = i
                    high = j
        return(max_sub_sum, low, high)
    
  • 利用 timeit 模塊測(cè)試兩個(gè)函數(shù)的性能

    print(di_cal_wrong(A))
    print(di_cal(A))
    
    t1 = Timer("di_cal([13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7])", "from __main__ import di_cal")
    print("di_cal ", t1.timeit(number=1000), "seconds")
    
    t1 = Timer("di_cal_wrong([13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7])", "from __main__ import di_cal_wrong")
    print("di_cal_wrong ", t1.timeit(number=1000), "seconds")
    
  • 輸出

    (43, 7, 10)
    (43, 7, 10)
    di_cal  0.017742687098883683 seconds
    di_cal_wrong  0.05528770369116012 seconds
    
  • 可以看到,很明顯 di_cal_wrong(A) 代碼的性能要差于 di_cal(A),所以實(shí)現(xiàn)算法時(shí)一定要細(xì)心,Python 容易使用,但是不能把集成好的功能當(dāng)作基本操作來(lái)使用。

解法二:分治策略

  • 假定我們要尋找 A[low..high] (假設(shè) low<high)的最大子數(shù)組,使用分治策略意味著要將這個(gè)數(shù)組分解為兩個(gè)規(guī)模盡量相等的子數(shù)組,即找到中央位置:mid=(low+high)/2。

  • 然后分解為兩個(gè)子數(shù)組:A[low..mid],A[mid+1..high]

  • 可以想象,A[low..high] 的最大子數(shù)組必然是以下三種情況之一

    • 完全位于左邊子數(shù)組中 A[low,mid] 中,此時(shí)可以解可以表示為:A[i..mid]
    • 完全位于右邊子數(shù)組中 A[mid,high] 中,此時(shí)可以解可以表示為:A[mid+1..high]
    • 跨越了中點(diǎn),此時(shí)解可以表示為 A[i..mid]+A[mid+1..j]
  • 這樣,在任何情況下,一個(gè)問題可以分為3個(gè)子問題,我們可以遞歸地求解 A[i..mid] 與 A[mid+1..high],因?yàn)檫@兩個(gè)子問題仍然是最大子數(shù)組問題,只是規(guī)模縮小了一半而已。

  • 因此我們先解決第三種情況:

    • 以 mid 為中心,向左向右依次找最大子數(shù)組,這個(gè)時(shí)候只需要線性時(shí)間就可以找到了

    • find_max_crossing_subarray:

      def find_cross_suming_subarray(A, mid, low, high):
          # 最大子數(shù)組橫跨中點(diǎn),所以最大子數(shù)組的左邊是A[i..mid],右邊是A[mid+1..j]
          # 求 A[i..mid] 可以直接用暴力求解法,從 mid 開始從左依次相加,判斷一下,然后賦值即可,求 A[mid+1..j] 是同樣的方法
          left_sum, right_sum = 0, 0
          max_left_sum, max_right_sum = -float('inf'), -float('inf')
          # 注意 range(start,stop,step),包括start,不包括stop,所以對(duì)應(yīng)的low-1 與 high+1
          for i in range(mid, low-1, -1):
              left_sum += A[i]
              if left_sum > max_left_sum:
                  max_left_sum = left_sum
                  low = i
          for j in range(mid+1, high+1, 1):
              right_sum += A[j]
              if right_sum > max_right_sum:
                  max_right_sum = right_sum
                  high = j
          return max_right_sum+max_left_sum, low, high
      
    • 有了第三種情況的處理,接下來(lái)可以編寫遞歸的函數(shù)了,注意要在開頭判斷跳出遞歸的條件:

      def divide_and_conquer(A, low, high):
          if low == high:
              return A[low], low, high
          mid = (low + high) // 2
      
          left_sum, left_low, left_high = divide_and_conquer(A, low, mid)
          print("left:", left_sum, left_low, left_high)
          right_sum, right_low, right_high = divide_and_conquer(A, mid+1, high)
          print("right:", right_sum, right_low, right_high)
          cross_sum, cross_low, cross_high = find_cross_suming_subarray(A, mid, low, high)
          print("cross:", cross_sum, cross_low, cross_high)
      
          if left_sum > right_sum and left_sum > cross_sum:
              return left_sum, left_low, left_high
          elif right_sum > left_sum and right_sum > cross_sum:
              return right_sum, right_low, right_high
          else:
              return cross_sum, cross_low, cross_high
      
    • 注意到遞歸后每次的結(jié)果都輸出到控制臺(tái)上,在處理遞歸問題時(shí),我發(fā)現(xiàn) log 比 debug 代碼要容易理解得多了,有興趣的朋友可以一步一步看看是怎么樣輸出的

      left: 13 0 0
      right: -3 1 1
      cross: 10 0 1
      left: 13 0 0
      left: -25 2 2
      right: 20 3 3
      cross: -5 2 3
      right: 20 3 3
      cross: 5 0 3
      left: 20 3 3
      left: -3 4 4
      right: -16 5 5
      cross: -19 4 5
      left: -3 4 4
      left: -23 6 6
      right: 18 7 7
      cross: -5 6 7
      right: 18 7 7
      cross: -21 5 7
      right: 18 7 7
      cross: 17 3 4
      left: 20 3 3
      left: 20 8 8
      right: -7 9 9
      cross: 13 8 9
      left: 20 8 8
      left: 12 10 10
      right: -5 11 11
      cross: 7 10 11
      right: 12 10 10
      cross: 25 8 10
      left: 25 8 10
      left: -22 12 12
      right: 15 13 13
      cross: -7 12 13
      left: 15 13 13
      left: -4 14 14
      right: 7 15 15
      cross: 3 14 15
      right: 7 15 15
      cross: 18 13 15
      right: 18 13 15
      cross: 16 8 15
      right: 25 8 10
      cross: 43 7 10
      (43, 7, 10)
      

解法三:聯(lián)機(jī)算法

  • 《算法導(dǎo)論》中的練習(xí)4.1-5提供了一種更快速地解決最大子數(shù)組的算法

  • 從數(shù)組的左邊界開始,從左往右處理,記錄到目前為止已經(jīng)處理過的最大子數(shù)組。

  • 假設(shè)我們已經(jīng)知道了 A[1..j] 的最大子數(shù)組,那么往右處理時(shí),可以遵從如下性質(zhì):

    • 數(shù)組 A[1...j+1] 的最大子數(shù)組,有兩種情況:

      • A[1...j] 的最大子數(shù)組

      • A[i...j+1]

  • 那么如何求得 A[i...j+1] 呢?

    • 首先不難想通:如果一個(gè)數(shù)組 a[1..r] 求和得到負(fù)值,那么下一次往右處理時(shí),可以直接把之前的記錄全部清空,因?yàn)橄麓尾僮鲿r(shí)的 a[r+1],還不如直接把自己當(dāng)作解(至少起點(diǎn)要從這里開始),因?yàn)?a[1..r]+a[r+1]<a[r+1]。

    • 所以只要某次操作時(shí),求和為負(fù),那么直接把和清0,重新計(jì)算最大子數(shù)組,并且把起點(diǎn)設(shè)置為下一個(gè)要操作的序數(shù)。

    • 代碼:

      def linear_time(A):
          sum, max_sub_sum, low, high, cur = 0, 0, 0, 0, 0
          for i in range(0, len(A)):
              sum += A[i]
              if sum > max_sub_sum:
                  max_sub_sum = sum
                  # 起點(diǎn)從0開始,從左往右操作
                  low = cur
                  high = i
              # 每當(dāng)和小于0時(shí),丟棄之前處理過的所有記錄,最大和清0,并且起點(diǎn)從下一位開始
              if sum < 0:
                  sum = 0
                  cur = i + 1
          return max_sub_sum, low, high
      
  • 在網(wǎng)上查閱了許久,上述解法應(yīng)該屬于聯(lián)機(jī)算法,不過挺容易理解的,并且時(shí)間復(fù)雜度的確是 O(n),常量空間,不需要輔助空間進(jìn)行,非常快。

百度百科:聯(lián)機(jī)算法是在任意時(shí)刻算法對(duì)要操作的數(shù)據(jù)只讀入(掃描)一次,一旦被讀入并處理,它就不需要在被記憶了。而在此處理過程中算法能對(duì)它已經(jīng)讀入的數(shù)據(jù)立即給出相應(yīng)子序列問題的正確答案。

解法四:動(dòng)態(tài)規(guī)劃

  • 這種解法同樣可以實(shí)現(xiàn)線性時(shí)間復(fù)雜度

  • 假設(shè)有一個(gè)數(shù)組a[1..n],若記 b[i] 為:以 a[i] 結(jié)尾的子數(shù)組的最大和,即

      b[i]=max{sum(a[j~k])}, 其中0<=j<=i,j<=k<=i。
    
  • 因此對(duì)于數(shù)組 a[0..n] 的最大子數(shù)組的和為

      max{b[0], b[1], b[2], .., b[n]}
    

    即求 b[] 的最大值

  • 由 b[i] 的定義可易知

      當(dāng) b[i-1]>0 時(shí),b[i]=b[i-1]+a[i]
      否則 b[i]=a[i]。
    
  • 故b[i]的動(dòng)態(tài)規(guī)劃遞歸式為:

      b[i] = max(b[i-1]+a[i], a[i]),1<=i<=n
    
  • 代碼如下:

      def dp(A):
          low, high = 0, 0
          B = list(range(len(A)))
          B[0] = A[0]
          max_sub_sum = A[0]
          for i in range(1, len(A)):
              if B[i-1] > 0:
                  B[i] = B[i-1] + A[i]
              else:
                  B[i] = A[i]
                  low = i
              if B[i] > max_sub_sum:
                  max_sub_sum = B[i]
                  high = i
          return max_sub_sum, low, high
    

感覺解法三、解法四很像,等到時(shí)候?qū)W習(xí)動(dòng)態(tài)規(guī)劃了,再來(lái)好好琢磨琢磨解法四的精髓。

找出數(shù)組中最大的兩個(gè)數(shù)

參考視頻:http://www.xuetangx.com/courses/course-v1:TsinghuaX+30240184_p1+sp/courseware/b64e822fe8bf4fc18e86a560087efebc/e794869faa9844528206ba3e9ab6b640/

我感覺視頻中前兩種方法有點(diǎn)點(diǎn)問題,把 A[0] 作為 x1, x2 的初值,有些刁鉆的測(cè)試用例會(huì)有錯(cuò)誤的輸出,所以我把初值都改為了負(fù)無(wú)窮大,相應(yīng)的,比較的次數(shù)會(huì)多一次,但是分析的思想沒變。

  • 輸入:一個(gè)數(shù)組

  • 輸出:數(shù)組中的最大值 x1 以及次大值 x2

  • 如果數(shù)組允許重復(fù)元素,那么必有 x1 > x2 ; 否則 x1 ≥ x2

    視頻中期望的輸出是 x1 ≥ x2,這樣的要求放寬了限制,把 A[0] 作為 x1, x2 的初值似乎沒毛病,但是為了深入思考,我決定嚴(yán)格限制輸出,這樣對(duì)編程思想的提高會(huì)有較大幫助。

  • 方法一——暴力枚舉法:

    • 第一趟循環(huán)找出數(shù)組的最大值 x1,n 次比較

    • 第二趟循環(huán)找出數(shù)組開頭到最大值序號(hào)之間的次大值

    • 第三趟循環(huán)找出數(shù)組最大值的序號(hào)到最后之間的次大值,與上面加起來(lái)是 n-1 次比較

    • 大約需要經(jīng)過 2n-1 次比較

      def max2_force(A):
          x1 = -float('inf')
          x2 = -float('inf')
          for i in range(len(A)):
              if A[i] > x1:
                  x1 = A[i]
                  j = i
          for i in range(j):
              if A[i] > x2:
                  x2 = A[i]
          for i in range(j + 1, len(A)):
              if A[i] > x2:
                  x2 = A[i]
          return x1, x2
      
  • 方法二——暴力枚舉法改進(jìn)版:

    • 只有一趟循環(huán),每次循環(huán)時(shí),先把當(dāng)前值與次大值比較,再進(jìn)一步把次大值與最大值比較

    • 最好情況:每次比較都小于次大值,所以比較次數(shù)為 n

    • 最壞情況:每次比較都大于次大值,所以比較次數(shù) 2n

      def max2_force_improve(A):
          x1 = -float('inf')
          x2 = -float('inf')
          for i in range(len(A)):
              if A[i] > x2:
                  x2 = A[i]
                  if x2 > x1:
                      x1, x2 = x2, x1
          return x1, x2
      
  • 方法三——分治法:

    • 時(shí)間復(fù)雜度:T(n) = 2*T(n/2) + 2 = 5n/3 - 2

      def max2_divide_and_conquer(A, low, high):
          if low == high:
              return A[low], A[low]
          elif low + 1 == high:
              if A[low] > A[high]:
                  return A[low], A[high]
              else:
                  return A[high], A[low]
          else:
              mid = (low + high) // 2
              x1_left, x2_left = max2_divide_and_conquer(A, low, mid)
              x1_right, x2_right = max2_divide_and_conquer(A, mid + 1, high)
              if x1_left > x1_right:
                  if x2_left > x1_right:
                      return x1_left, x2_left
                  else:
                      return x1_left, x1_right
              else:
                  if x2_right > x1_left:
                      return x1_right, x2_right
                  else:
                      return x1_right, x1_left
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗(yàn)。 張土汪:刷leetcod...
    土汪閱讀 12,769評(píng)論 0 33
  • 有多少人,從無(wú)話不談到無(wú)話可談; 有多少緣,從一朝相逢到一夕離散。 緣分的深淺,總是忽近忽遠(yuǎn); 人心的冷暖,總是一...
    趙家大百合閱讀 336評(píng)論 0 0
  • 有壓力就是動(dòng)力,學(xué)習(xí)需要靠壓力來(lái)提速,但是一味的給自己施加壓力,自己會(huì)累垮,因此有好的學(xué)習(xí)方法很重要。也是對(duì)時(shí)間的...
    周游周游閱讀 280評(píng)論 0 0
  • 名日精進(jìn)【打卡第200天】: 姓名:余成杰 公司:貞觀電器 盛和塾《六項(xiàng)精進(jìn)》224期學(xué)員 【知-學(xué)習(xí)】 《六項(xiàng)精...
    余成杰閱讀 105評(píng)論 0 0