期末考要來了,最近小秋正在從零開始複習算法相關知識....算法
帥地:據說你最近正在臨時飽佛教複習各類算法?數組
小秋:對啊,算法太難了,把我頭都搞大了,不過,感受本身複習的好像還不錯。bash
帥地:那我找一道簡單的題考考你?數據結構
小秋:好啊,好啊,正好能夠試試水。函數
帥地:給你一個有序數組,例如工具
而後我給出一個元素 target,你返回它對應數組的下標,若是數組中不存在這個元素的話,則返回 -1。例如開發工具
一、我給出 target = 18,則你須要返回 5。ui
二、我給出 target = 1,你須要返回 0。spa
三、我給出 target = 10,因爲數組中不存在 10 這個元素,因此你須要返回 -1。3d
小秋:這也太簡單了吧,我從左到右遍歷數組全部元素,就能夠找出來了。
帥地:這是一種方法,不過你這種方法的時間複雜度爲 O(n),有沒有時間複雜度上更小的方法呢?
小秋:那我能夠採用哈希表來存儲,把數組元素做爲 key,對應的下標做爲 value,這樣的話,之後須要查找某個元素了,我就能夠在時間複雜度爲 O(1) 找到對應的元素下標了。
帥地:你這種方法,須要用哈希表來存放元素,雖然時間複雜度爲 O(1),可是空間複雜度爲 O(n) 了,你能不能在時間複雜度和空間複雜度折中一下,找出更加優美的方法呢?
小秋:好像,,目前沒啥思路。
帥地:你據說過二分查找嗎?
小秋:二分查找?什麼鬼?
帥地:這道題就能夠用二分查找來解決了,我來給你講講吧。
小秋:好啊,好啊。
帥地:其實呢,對於這道題,因爲數組是有序的,咱們每次在查找的時候,能夠直接從中間元素開始比較,例如咱們要查找 target = 10,這個元素,咱們把 target 與數組中間的那個元素 15,進行比較。
(1)若是 target 比 15 小,那麼 15 以及 15 右邊的全部元素必定比 target 大,因此 target 只能存在於 15 的左邊元素中。
(2)若是 target 比 15 大,那麼 15 以及 15 左邊的全部元素必定比 target 小,因此 target 只能存在於 15 的右邊元素中。
(3)若是 target 與 15 相等,則直接把 15 對應的下標返回便可。
在這個例子中,target = 10 比 15 小,因此 target 只可能存在於 15 的左邊元素中
接下來咱們只須要在左半部分查找這個元素就能夠了,右半部分的元素能夠不用管了,你看,經過這種方式,只須要一次比較,咱們就能夠把查找的範圍縮小了一半。
接着咱們繼續把 target 與中間元素比較,這時候中間元素是 5,15比 5 大,因此 target 只可能存在於 5 右邊的元素中
接下來咱們繼續查找,這時中間元素是 10,和 target 相等,因此直接把 10 的下標 index = 2 返回。
帥地:小秋啊,這種每次都從中間元素開始比較,而且一次比較後就能把查找範圍縮小一半的方法,就叫作二分查找了,這種二分查找的時間複雜度是 O(logn),空間複雜度是 O(1),能夠說很是快的了。
小秋:那什麼狀況下可使用二分查找這種方法呢?
帥地:要使用二分查找,給的數據須要具有兩個基本的特性
(1)給的數據是有序的。
(2)給的數據支持隨機訪問。
小秋:什麼是隨機訪問呢?
帥地:例如像數組就支持隨機訪問了,例如你要訪問第 5 個元素,那麼你就能夠用下標爲 4,即 arr[4] 來快速訪問第五個元素了。而鏈表就不支持隨機訪問了,例如你要訪問鏈表的第 5 個元素,你沒法像數組那樣,直接用下標來定位,只能從鏈表頭部一個一個遍歷到第五個。
小秋:哦,我知道了,只有支持隨機訪問,咱們才能根據數組最左邊和最右邊的下標,直接定位到數組的中間元素。
帥地:是的,那你能夠根據剛纔那道題寫一下代碼嗎?
小秋:沒問題。
public static int binarySearch(int target, int[] arr) {
// 數組最左邊元素下標
int left = 0;
// 數組最右邊元素下標
int right = arr.length - 1;
while (left <= right) {
// 中間元素下標
int mid = (right + left) / 2;
if (arr[mid] > target) {
right = mid - 1;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
return arr[mid];
}
}
return -1;
}
複製代碼
帥地:你寫的基本正確,不過有個小問題,就是算中間元素下標那裏
mid = (left + right) / 2;
複製代碼
你這種方法有時候會產生溢出的哦。例如,咱們知道 int 整數的最大值大概是 2^31 - 1 大概爲 21 億。而 left 和 right 這兩個數相加是有可能超過 21 億的,例如 left = 12億,right = 13 億。這個時候,兩個數的和超過了最大值,就會產生溢出了。
小秋:那該怎麼寫?
帥地:正確的寫法應該是這樣的
mid = left + (right - left) / 2;
複製代碼
這樣,就能保存不會出現溢出的狀況了。
帥地:其實在咱們的生活中,二分查找也是有挺多應用的,例如用二分查找來作壞事。
小秋:壞事?能夠給我舉例看看嗎?
帥地:有時候臨近一些比賽了,例如全世界性的足球大賽,有時候咱們會收到一些郵件,有人謊稱他會神預算,例現在天是德國和法國比賽,他會跟你說必定是德國勝,而後跟另一部分人今天必定是法國勝。
而德國和法國,總有一我的會勝,那麼他能夠跟 10000人說德國勝,10000人說法國勝。咱們假如是德國勝了,那麼在 10000 人看來它的預算是正確的。
接着德國和俄羅斯比賽,它會在那 10000 人中,跟 5000人說德國勝,5000人說俄羅斯勝。那麼在 5000 看來它的預算仍是正確的....
就這樣,每次它都從被他預算正確的那一部分人繼續吹他會神預算,那麼在有些人看來,他果然連續預算正確,這個時候,這些人可能就會認爲,他真的有神預算的能力,因而,可能就會相信它說的話了,進而就會被他所欺騙。
小秋:雖然我沒收到這類郵件,可是我常常收到一些賭博相關的短信
帥地:是的,這些,就是利用的二分查找的思想了。
帥地:小秋啊,剛纔給你講的那道題,能夠說是最簡單的了,其實,對於二分查找,有不少變形題的,每每不會那麼簡單。
小秋:那你能夠給我出幾道,看我能不能現學現賣。
帥地:那我給你兩道題吧。
一、實現 pow(x, n)函數:即計算 x 的 n 次方。不許使用我以前給你講的位運算:【算法技巧】位運算裝逼指南。
二、搜索選擇排序數組:假設按照升序排序的數組在預先未知的某個點上進行了旋轉。( 例如,數組 [0,1,2,4,5,6,7] 可能變爲 [4,5,6,7,0,1,2] )。
搜索一個給定的目標值,若是數組中存在這個目標值,則返回它的索引,不然返回 -1 。你能夠假設數組中不存在重複的元素。你的算法時間複雜度必須是 O(log n) 級別。
示例 1:
輸入: nums = [4,5,6,7,0,1,2], target = 0
輸出: 4
示例 2:
輸入: nums = [4,5,6,7,0,1,2], target = 3
輸出: -1
複製代碼
這兩道題,能夠說是中等難度的變形題了,跟我說說怎麼作?
小秋:這....,我纔剛學完,能不能給我點時間讓讓想一想?
帥地:大概多長呢?
小秋:不長,兩天時間就能夠了。
帥地:兩天 還不長...... 好吧,那兩天後見。
此次主要講解了二分查找的基本思想以及生活在的例子,二分查找思想雖然不難,不過卻有不少不容易的題,後面的問題,若是小秋沒作出來,我就下次給你們講解。若是小秋作出來的,咱們就來聽聽他怎麼作的,敬請期待。
若是你以爲這篇內容對你挺有啓發,爲了讓更多的人看到這篇文章:不妨
一、點贊,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓 -_-)
二、關注我和專欄,讓咱們成爲長期關係
三、關注公衆號「苦逼的碼農」,主要寫算法、計算機基礎之類的文章,裏面已有100多篇原創文章