LeetCode——二分查找
二分查找
目錄
1. 二分查找法
正常實現(xiàn)
public int binarySearch(int[] nums, int key) {int l = 0, h = nums.length - 1;while (l <= h) {int m = l + (h - l) / 2;if (nums[m] == key) {return m;} else if (nums[m] > key) {h = m - 1;} else {l = m + 1;}}return -1;}時間復(fù)雜度
二分查找也稱為折半查找,每次都能將查找區(qū)間減半,這種折半特性的算法時間復(fù)雜度為 O(logN)。
m 計算
有兩種計算中值 m 的方式:
- m = (l + h) / 2
- m = l + (h - l) / 2l + h 可能出現(xiàn)加法溢出,也就是說加法的結(jié)果大于整型能夠表示的范圍。但是 l 和 h 都為正數(shù),因此 h - l 不會出現(xiàn)
加法溢出問題。所以,最好使用第二種計算法方法。
未成功查找的返回值
循環(huán)退出時如果仍然沒有查找到 key,那么表示查找失敗。可以有兩種返回值:
- -1:以一個錯誤碼表示沒有查找到 key
- l:將 key 插入到 nums 中的正確位置
變種
二分查找可以有很多變種,變種實現(xiàn)要注意邊界值的判斷。例如在一個有重復(fù)元素的數(shù)組中查找 key 的最左位置的實現(xiàn)如下:
public int binarySearch2(int[] nums, int key) {int l = 0, h = nums.length - 1;while (l < h) {int m = l + (h - l) / 2;if (nums[m] >= key) {h = m ;} else {l = m + 1;}}return l;}該實現(xiàn)和正常實現(xiàn)有以下不同:
- h 的賦值表達(dá)式為 h = m
- 循環(huán)條件為 l < h
- 最后返回 l 而不是 -1
在 nums[m] >= key 的情況下,可以推導(dǎo)出最左 key 位于 [l, m] 區(qū)間中,這是一個閉區(qū)間。h 的賦值表達(dá)式為 h =m,因為 m 位置也可能是解。
在 h 的賦值表達(dá)式為 h = m 的情況下,如果循環(huán)條件為 l <= h,那么會出現(xiàn)循環(huán)無法退出的情況,因此循環(huán)條件只能是 l < h。以下演示了循環(huán)條件為 l <= h 時循環(huán)無法退出的情況
當(dāng)循環(huán)體退出時,不表示沒有查找到 key,因此最后返回的結(jié)果不應(yīng)該為 -1。為了驗證有沒有查找到,需要在調(diào)用端判斷一下返回位置上的值和 key 是否相等。
2. 求開方
public int mySqrt(int x) {if (x <= 1) {return x;}int l = 1;int h = x;while (l <= h) {int mid = l + (h - l) / 2;int sqrt = x / mid;if (mid == sqrt) {return sqrt;} else if (mid > sqrt) {h = mid - 1;} else {l = mid + 1;}}return h;}3. 大于給定元素的最小元素
題目描述:給定一個有序的字符數(shù)組 letters 和一個字符 target,要求找出 letters 中大于 target 的最小字符,如果找不到就返回第 1 個字符。
4. 有序數(shù)組的 Single Element
題目描述:一個有序數(shù)組只有一個數(shù)不出現(xiàn)兩次,找出這個數(shù)。
要求以 O(logN) 時間復(fù)雜度進(jìn)行求解,因此不能遍歷數(shù)組并進(jìn)行異或操作來求解,這么做的時間復(fù)雜度為 O(N)。
令 index 為 Single Element 在數(shù)組中的位置。在 index 之后,數(shù)組中原來存在的成對狀態(tài)被改變。如果 m 為偶數(shù),并且 m + 1 < index,那么 nums[m] == nums[m + 1];m + 1 >= index,那么 nums[m] != nums[m + 1]。
從上面的規(guī)律可以知道,如果 nums[m] == nums[m + 1],那么 index 所在的數(shù)組位置為 [m + 2, h],此時令 l = m +2;如果 nums[m] != nums[m + 1],那么 index 所在的數(shù)組位置為 [l, m],此時令 h = m。
因為 h 的賦值表達(dá)式為 h = m,那么循環(huán)條件也就只能使用 l < h 這種形式
public int singleNonDuplicate(int[] nums) {int l = 0;int h = nums.length - 1;while (l < h) {int m = l + (h - l) / 2;while (m % 2 == 1) {m--; // 保證 l/h/m 都在偶數(shù)位,使得查找區(qū)間大小一直都是奇數(shù)}if (nums[m] == nums[m + 1]) {l = m + 2;} else {h = m;}}return nums[l];}5. 第一個錯誤的版本
題目描述:給定一個元素 n 代表有 [1, 2, …, n] 版本,在第 x 位置開始出現(xiàn)錯誤版本,導(dǎo)致后面的版本都錯誤。可以調(diào)用 isBadVersion(int x) 知道某個版本是否錯誤,要求找到第一個錯誤的版本。
如果第 m 個版本出錯,則表示第一個錯誤的版本在 [l, m] 之間,令 h = m;否則第一個錯誤的版本在 [m + 1, h] 之間,令 l = m + 1。
因為 h 的賦值表達(dá)式為 h = m,因此循環(huán)條件為 l < h。
public int firstBadVersion(int n) {int l = 1, h = n;while (l < h) {int mid = l + (h - l) / 2;if (isBadVersion(mid)) {h = mid;} else {l = mid + 1;}}return l;}6. 旋轉(zhuǎn)數(shù)組的最小數(shù)字
public int findMin(int[] nums) {int l = 0, h = nums.length - 1;while (l < h) {int m = l + (h - l) / 2;if (nums[m] <= nums[h]) {h = m;} else {l = m + 1;}}return nums[l];}7. 查找區(qū)間
public int[] searchRange(int[] nums, int target) {int first = binarySearch(nums, target);int last = binarySearch(nums, target + 1) - 1;if (first == nums.length || nums[first] != target) {return new int[]{-1, -1};} else {return new int[]{first, Math.max(first, last)};}}private int binarySearch(int[] nums, int key) {int l = 0, h = nums.length - 1;while (l < h) {int m = l + (h - l) / 2;if (nums[m] >= key) {h = m;} else {l = m + 1;}}return l;}總結(jié)
以上是生活随笔為你收集整理的LeetCode——二分查找的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LeetCode——分治
- 下一篇: LeetCode——BFS