1. 程式人生 > 實用技巧 >動漫演算法梳理

動漫演算法梳理

【寫在前言

最近關注了好幾個好友專門講演算法的公主號,趕腳還不錯,本著“分享”、“共進”的初心,在徵得本人的同意之下,特此將原內容經原作者本人同意授權後,重新編輯、排版、整理到此處。

在此,特別感謝小夕學演算法,袁廚的演算法小屋等原創作者大牛。

好了,話不多說,我要開啟學習,和大家共同進步了,嘻嘻~~~

【正文開始】

本文摘自【小夕】,點選可看原文

題目:

把一個數組最開始的若干個元素搬到陣列的末尾,我們稱之為陣列的旋轉。

輸入一個遞增排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。例如,陣列[3,4,5,1,2][1,2,3,4,5]的一個旋轉,該陣列的最小值為1。

示例1:
輸入:[
3,4,5,1,2] 輸出:1
示例2: 輸入:[
2,2,2,0,1] 輸出:0

來源:力扣(LeetCode)、劍指offer11
連結:https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof

有人說,像我下面這樣一個迴圈不就A了嗎?而且擊敗率100%,還有什麼知識點?

如果你也這樣想,那就大錯特錯了。且聽我慢慢道來~~~

那麼疑問來了,為什麼這樣寫不是最優的呢?

因為這就涉及到了複雜度的問題,一般情況下,單獨一個for迴圈的複雜度是O(n),但是明顯地,這道題不是讓你去迴圈整個陣列的。

在有序陣列中查詢某個元素,一般較好的方法是二分法,因為二分法可以將複雜度降為logN;

那麼在二分法中,一般會有比較基準值,也就是target,有規定的話,就按照規定來取,沒有的話一般按照最左側或最右側(也可稱為左右端值)取為target,這在左神的演算法課裡講過;

在這道題目中是沒有目標值的,那麼用中間值和低高位(左右端值)進行比較,看處於前半部分的是遞增序列還是後半部分的是遞增序列(旋轉陣列,比如說[4,5,6,7,8,1,2,3]前半部分是遞增序列[4,5,6,7,8],後半部分遞增序列[1,2,3]),進行操作縮小範圍。然後遞迴在新的範圍內再次利用該思想進行查詢。

在這裡我新增一個小栗子,方便大家閱讀:

比如:陣列[4,5,6,7,8,1,2,3],陣列長度為7,中間元素是陣列長,7/2=3,所在位置的元素為7。那麼,就該陣列就被分為兩部分[4,5,6,7],[8,1,2,3],前面的陣列為遞增,不用管,後面的為非遞增,所以下一次就在右側數組裡繼續查詢,那麼左端值元素就變為8,右端值元素不變還是3。這種情況就是在右側查詢;

同樣的還有在左側陣列查詢:

比如陣列[7,8,1,2,3,4,5,6],陣列長度為7,中間元素是陣列長,7/2=3,所在位置的元素為2。那麼,就該陣列就被分為兩部分[7,8,1,2],[3,4,5,6],前面的陣列為非遞增,後面的為遞增,不用管,所以下一次就在左側數組裡繼續查詢,那麼右端元素值就變為2,左端值元素不變還是7。

這裡我們把target 看作是左端點,來進行分析,那就要分析以下三種情況,看是否可以達到上述的目標,

  • 情況1,陣列是[1,2,3,4,5],左端點left的陣列下標等於0,右端點right陣列下標等於4。陣列是遞增陣列,沒有發生扭轉。又因為arr[left]<arr[right],所以最小值就是arr[left];
  • 情況2,陣列是[4,5,6,7,8,1,2,3],左端點left的陣列下標等於0,右端點right陣列下標等於7。arr[left] > arr[right],發生了扭轉。又arr[mid] = 7 > arr[left] = 4,target也就是arr[left]為左端點 4,arr[mid] > target(也就是arr[left]), 說明[first ... mid] 都是 >= target(也就是arr[left]) 的,因為原始陣列是非遞減,所以可以確定答案為 [mid+1...last]區間,所以left= mid + 1;
  • 情況3,陣列是[5 6 1 2 3 4],左端點left的陣列下標等於0,右端點陣列下標等於5,mid下標是2。arr[mid] = 1 < arr[right]=4,arr[mid] 為 1, target為右端點 4,arr[mid] < target(arr[right]), 說明答案肯定不在[mid+1...right],但是arr[mid] 有可能是答案,所以答案在[first, mid]區間,所以right= mid;
  • 情況4,arr[mid] 既等於arr[left]又等於arr[right],需要特殊處理。進行left++,如果arr[left] < arr[right]那麼直接返回arr[left]。

如果上述還沒看懂,請看下面的解析圖:

以array = [4,5,6,7,8,1,2,3]為例:宣告:下圖中的high 和right一樣的意思。

array[mid] = 7 > array[left] = 4是第二種情況,由於遞增最小值只可能在mid的右側區間,故新的引數:left = mid+1 = 3+1 =4;mid = (left + high)/2 = (4+7)/2 = 5。新的圖畫如下:

array[mid] = 5 < array[left] = 8是第三種情況,由於遞增最小值只可能在mid的左側區間(包括mid),故新的引數: high = mid = 5;mid = (left + high)/2= (4+5)/2 = 4 。

新的圖畫如下:

此時,array[mid] = 8 < array[left] = 8是第四種情況,新的引數如下: left = left +1 = 4+1 = 5,此時,left = high,故找到最小值,也就是array[5] = 1,跳出迴圈結束。其實新的圖,也可如下看待:

第四種情況,單獨將left++,目的是為了防止像[2,0,2,2,2]類似於這樣的陣列出現。

如果像這樣的陣列,那麼,根據前述,一開始array[left] = array[mid] 的,前三種情況都不符合。所以需要特殊處理,就是第四種。

其實好好想一想,第二種是array[left] < array[mid] ,第三種是array[left] > array[mid] ,所以肯定還有一種是array[left] = array[mid]。但這個時候你就不能判斷最小值是在左區間還是右區間。比如:陣列[2,0,2,2,2]中,最小值0就是在左區間;陣列[2,2,2,2,2,0,2,2]中,最小值0就是在右區間內。所以,為了確定是在哪一個區間內,我們只好將left+1,這樣新的陣列也縮小了,就可以繼續比對,繼續判斷新的array[left] 和array[mid] 的大小來確定最小值在哪一側。原文中關於這一點還有圖畫,我在這裡就不重複敘述了,不懂得可以移步,順帶關注一波原博主,哈哈哈~~~

Over。。。