二分查找

------

二分查找基本版及其各種變形的匯總

思想:? 二分搜索的核心就是**循環(huán)結(jié)束條件**和**左右邊界迭代規(guī)則**

#### 一. 基本二分

##### 基本的二分查找

我們這個算法中使用的是前者 `[left, right]` 兩端都閉的區(qū)間盈简。**這個區(qū)間其實(shí)就是每次進(jìn)行搜索的區(qū)間**

當(dāng)然柒啤,找到了目標(biāo)值的時候可以終止

if(nums[mid] == target)

?? ? ? ? return mid;

```java

int binarySearch(int[] nums, int target) {

? ? int left = 0, right = nums.length - 1;

? ? while(left <= right) {

? ? ? ? int mid = left + (right - left) / 2;

? ? ? ? if (nums[mid] < target) {

? ? ? ? ? ? left = mid + 1;

? ? ? ? } else if (nums[mid] > target) {

? ? ? ? ? ? right = mid - 1;

? ? ? ? } else if(nums[mid] == target) {

? ? ? ? ? ? // 直接返回

? ? ? ? ? ? return mid;

? ? ? ? }

? ? }

? ? // 直接返回

? ? return -1;

}

```

#### 二. 進(jìn)階二分

##### 2.1 尋找左側(cè)邊界的二分查找

```java

int leftBound(int[] nums, int target) {

? ? int left = 0, right = nums.length - 1;

? ? while (left <= right) {

? ? ? ? int mid = left + (right - left) / 2;

? ? ? ? if (nums[mid] < target) {

? ? ? ? ? ? left = mid + 1;

? ? ? ? } else if (nums[mid] > target) {

? ? ? ? ? ? right = mid - 1;

? ? ? ? } else if (nums[mid] == target) {

? ? ? ? ? ? // 別返回挚冤,鎖定左側(cè)邊界

? ? ? ? ? ? right = mid - 1;

? ? ? ? }

? ? }

? ? // 最后要檢查 left 越界的情況

? ? if (left >= nums.length || nums[left] != target)

? ? ? ? return -1;

? ? return left;

}

```

##### 2.2 尋找右側(cè)邊界的二分查找

```java

int rightBound(int[] nums, int target) {

? ? int left = 0, right = nums.length - 1;

? ? while (left <= right) {

? ? ? ? int mid = left + (right - left) / 2;

? ? ? ? if (nums[mid] < target) {

? ? ? ? ? ? left = mid + 1;

? ? ? ? } else if (nums[mid] > target) {

? ? ? ? ? ? right = mid - 1;

? ? ? ? } else if (nums[mid] == target) {

? ? ? ? ? ? // 別返回夕冲,鎖定右側(cè)邊界

? ? ? ? ? ? left = mid + 1;

? ? ? ? }

? ? }

? ? // 最后要檢查 right 越界的情況

? ? if (right < 0 || nums[right] != target)

? ? ? ? return -1;

? ? return right;

}

```

注意點(diǎn):**注意搜索區(qū)間 和 while的終止條件**

##### 2.3 查找最后一個等于或者小于target的元素

關(guān)鍵詞:**右邊界**

查找最后一個等于或者小于target的元素译断,也就是說如果查找target值的元素有好多個箱季,返回這些元素最右邊的元素下標(biāo)玄柏;如果沒有等于target值的元素兼呵,則返回小于target的最右邊元素下標(biāo)

```java

// 查找最后一個等于或者小于target的元素

int findLastEqualSmaller(int[] nums, int target) {

? ? int left = 0;

? ? int right = nums.length - 1;

? ? // 這里必須是 <=

? ? while (left <= right) {

? ? ? ? int mid = (left + right) / 2;

? ? ? ? if (nums[mid] > target) {

? ? ? ? ? ? right = mid - 1;

? ? ? ? }

? ? ? ? else {

? ? ? ? ? ? left = mid + 1;

? ? ? ? }

? ? }

? ? return right;? //必須返回right

}

```

##### 2.4 查找最后一個小于target的元素

返回小于target的最右邊元素下標(biāo)

```java

// 查找最后一個小于target的元素

int findLastSmaller(int[] nums, int target) {

? ? int left = 0;

? ? int right = nums.length - 1;

? ? // 這里必須是 <=

? ? while (left <= right) {

? ? ? ? int mid = (left + right) / 2;

? ? ? ? if (nums[mid] >= target) {

? ? ? ? ? ? right = mid - 1;

? ? ? ? }

? ? ? ? else {

? ? ? ? ? ? left = mid + 1;

? ? ? ? }

? ? }

? ? return right;? //必須返回right

}

```

##### 2.5 查找第一個等于或者大于target的元素

? 關(guān)鍵詞:左邊界

 查找第一個等于或者大于target 的元素畸写,也就是說如果查找target值的元素有好多個驮瞧,返回這些元素最左邊的元素下標(biāo);如果沒有等于target值的元素枯芬,則返回大于target的最左邊元素下標(biāo)

```java

// 查找第一個等于或者大于key的元素

int findFirstEqualLarger(int[] nums, int target) {

? ? int left = 0;

? ? int right = nums.length - 1;

? ? // 這里必須是 <=

? ? while (left <= right) {

? ? ? ? int mid = (left + right) / 2;

? ? ? ? if (nums[mid] >= target) {

? ? ? ? ? ? right = mid - 1;

? ? ? ? }

? ? ? ? else {

? ? ? ? ? ? left = mid + 1;

? ? ? ? }

? ? }

? ? return left;

}

```

##### 2.6 查找第一個大于target的元素

```java

// 查找第一個大于key的元素

int findFirstLarger(int[] nums, int target) {

? ? int left = 0;

? ? int right = nums.length - 1;

? ? // 這里必須是 <=

? ? while (left <= right) {

? ? ? ? int mid = (left + right) / 2;

? ? ? ? if (nums[mid] > target) {

? ? ? ? ? ? right = mid - 1;

? ? ? ? }

? ? ? ? else {

? ? ? ? ? ? left = mid + 1;

? ? ? ? }

? ? }

? ? return left;

}

```

第一個大于等于target 或者? 最后一個小于等于target的元素论笔,擴(kuò)展開來,

最小條件:? **第一個滿足條件的** 千所,左邊界

**案例一:阿珂吃香蕉**:找到第一個滿足條件的狂魔,比 該值大的都滿足,所以是找左邊界

滿足條件指的是:canFinish(piles, mid, H)

https://leetcode-cn.com/problems/koko-eating-bananas/

```java

public int minEatingSpeed(int[] piles, int H) {

? ? ? ? int maxSpeed = getMax(piles);

? ? ? ? int left= 1;

? ? ? ? int right = maxSpeed;

? ? ? ? while(left <= right){

? ? ? ? ? ? int mid= left +(right -left)/2;

? ? ? ? ? ? if(canFinish(piles, mid, H)){? //查找左邊界淫痰,找到第一個滿足 條件的

? ? ? ? ? ? ? ? right = mid-1;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? left = mid + 1;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return left;

? ? }

? ? // 時間復(fù)雜度 O(N)

? ? boolean canFinish(int[] piles, int speed, int H) {

? ? ? ? int time = 0;

? ? ? ? for (int n : piles) {

? ? ? ? ? ? time += timeOf(n, speed);

? ? ? ? }

? ? ? ? return time <= H;

? ? }

? ? int timeOf(int n, int speed) {

? ? ? ? return (n / speed) + ((n % speed > 0) ? 1 : 0);

? ? }

? ? int getMax(int[] piles) {

? ? ? ? int max = 0;

? ? ? ? for (int n : piles)

? ? ? ? ? ? max = Math.max(n, max);

? ? ? ? return max;

? ? }

```

案例二: 給定一個包含 n + 1 個整數(shù)的數(shù)組 nums最楷, 找出重復(fù)數(shù)

滿足條件: cnt[i] > i;

```java

? ? public int findDuplicate_BinarySearch(int[] nums) {

? ? ? ? int n? = nums.length;

? ? ? ? int l =1, r = n-1;

? ? ? ? while(l <=r ){

? ? ? ? ? int mid = l + (r -l)/2;

? ? ? ? ? ? int cnt = cntLessEqual(nums, mid);

? ? ? ? ? ? if(cnt > mid){//? 查找第一個滿足條件的 :cnt[mid] > mid的最左邊

? ? ? ? ? ? ? ? r = mid -1;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? l = mid +1;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return l;

? ? }

? ? private int cntLessEqual(int[] nums, int target){

? ? ? ? int cnt = 0;

? ? ? ? for(int n:nums){

? ? ? ? ? ? if(n <= target){

? ? ? ? ? ? ? ? cnt++;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? return cnt;

? ? }

```

#### 三. 二分查找變種總結(jié)

```java

// 這里必須是 <=

while (left <= right) {

? ? int mid = (left + right) / 2;

? ? if (array[mid] ? key) {

? ? ? ? //... right = mid - 1;

? ? }

? ? else {

? ? ? ? // ... left = mid + 1;

? ? }

}

return xxx;

```

1. 首先判斷出是返回left,還是返回right

? 因?yàn)槲覀冎雷詈筇鰓hile (left <= right)循環(huán)條件是right < left待错,且right = left - 1籽孙。最后right和left一定是卡在"邊界值"的左右兩邊,如果是比較值為key火俄,查找小于等于(或者是小于)key的元素犯建,則邊界值就是等于key的所有元素的最左邊那個,其實(shí)應(yīng)該返回left瓜客。

?   以數(shù)組{1, 2, 3, 3, 4, 5}為例胎挎,如果需要查找第一個等于或者小于3的元素下標(biāo),我們比較的key值是3忆家,則最后left和right需要滿足以下條件:

? ![img](http://images2015.cnblogs.com/blog/772134/201608/772134-20160813153101328-934983178.png)

?   我們比較的key值是3犹菇,所以此時我們需要返回left

2. 判斷出比較符號

? ```java

? int mid = (left + right) / 2;

? if (array[mid] ? key) {

? ? ? //... right = xxx;

? }

? else {

? ? ? // ... left = xxx;

? }

? ```

?  也就是這里的 if (array[mid] ? key) 中的判斷符號,結(jié)合步驟1和給出的條件芽卿,如果是查找小于等于key的元素揭芍,則知道應(yīng)該使用判斷符號>=,因?yàn)槭且祷豯eft卸例,所以如果array[mid]等于或者大于key称杨,就應(yīng)該使用>=肌毅,以下是完整代碼

? ```java

? // 查找小于等于key的元素

? int mid = (left + right) / 2;

? if (array[mid] >= key) {

? ? ? right = mid - 1;

? }

? else {

? ? ? left = mid + 1;

? }

? ```

### 另類二叉查找

https://leetcode-cn.com/problems/find-peak-element/

尋找峰值

![image.png](https://pic.leetcode-cn.com/802bad70c4444bf708f4c63e30e054a33c27ace43b3c7b4fa64a0ffb8201fb7d-image.png)

峰值元素是指其值大于左右相鄰值的元素。

給定一個輸入數(shù)組 nums姑原,其中 nums[i] ≠ nums[i+1]悬而,找到峰值元素并返回其索引。

數(shù)組可能包含多個峰值锭汛,在這種情況下笨奠,返回任何一個峰值所在位置即可。

你可以假設(shè) nums[-1] = nums[n] = -∞

```java

public int findPeakElement(int[] nums) {


? ? ? ? int l = 0, r = nums.length - 1;

? ? ? ? while (l < r) {

? ? ? ? ? ? int mid =? l + (r -l)/2;

? ? ? ? ? ? if (nums[mid] > nums[mid + 1])

? ? ? ? ? ? ? ? r = mid;

? ? ? ? ? ? else

? ? ? ? ? ? ? ? l = mid + 1;

? ? ? ? }

? ? ? ? return l;

? ? }

```

參考:

https://labuladong.gitbook.io/algo/suan-fa-si-wei-xi-lie/er-fen-cha-zhao-xiang-jie

https://blog.csdn.net/bat67/article/details/72049104

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末唤殴,一起剝皮案震驚了整個濱河市般婆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌朵逝,老刑警劉巖蔚袍,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異配名,居然都是意外死亡啤咽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門渠脉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宇整,“玉大人,你說我怎么就攤上這事连舍。” “怎么了涩哟?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵索赏,是天一觀的道長。 經(jīng)常有香客問我贴彼,道長潜腻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任器仗,我火速辦了婚禮融涣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘精钮。我一直安慰自己威鹿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布轨香。 她就那樣靜靜地躺著忽你,像睡著了一般。 火紅的嫁衣襯著肌膚如雪臂容。 梳的紋絲不亂的頭發(fā)上科雳,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天根蟹,我揣著相機(jī)與錄音,去河邊找鬼糟秘。 笑死简逮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尿赚。 我是一名探鬼主播散庶,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吼畏!你這毒婦竟也來了督赤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤泻蚊,失蹤者是張志新(化名)和其女友劉穎躲舌,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體性雄,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡没卸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了秒旋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片约计。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖迁筛,靈堂內(nèi)的尸體忽然破棺而出煤蚌,到底是詐尸還是另有隱情,我是刑警寧澤细卧,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布尉桩,位于F島的核電站,受9級特大地震影響贪庙,放射性物質(zhì)發(fā)生泄漏蜘犁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一止邮、第九天 我趴在偏房一處隱蔽的房頂上張望这橙。 院中可真熱鬧,春花似錦导披、人聲如沸屈扎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽助隧。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間并村,已是汗流浹背巍实。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哩牍,地道東北人棚潦。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像膝昆,于是被迫代替她去往敵國和親丸边。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355