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