我們經常會用到二分查找
二分查找應該很多人都會寫了,今天要寫一個用二分查找找到小於k的最大值的時候看了很久不懂他設計的思路,后來想通了,記錄一下。
所以這篇主要是講 用二分查找找到小於k的最大值和大於k的最大值。
二分查找查找指定值
這個挺簡單的,直接上代碼吧
//獲取值是k的位置,找不到則返回-1
public static int getK(int[] a, int k){
if(a.length == 0){
return -1;
}
int l = 0;
int r = a.length - 1;
//注意這里的判斷條件,是必須允許 l = r 的情況存在的
//因為可能會出現剛好到最后左邊指針到右邊指針只有1個元素,而且這個元素恰恰就是我們想找的k
while (l <= r){
int mid = (l + r) / 2;
//如果mid元素已經是k了,那么直接返回
if (a[mid] == k){
return mid;
//查找左邊的
}else if(a[mid] > k) {
r = mid - 1;
//查找右邊的
}else {
l = mid + 1;
}
}
//如果沒有找到指定的值,那么直接返回-1
return -1;
}
用二分查找找到小於或者等於k的最大值
思路:如果mid元素是小於或等於k,往右找,如果大於k,往左邊找,直到找到一個值,最接近與k的。
看代碼中標注了 處地方,下面解釋為什么如此設計
//獲取值<=k的最大值
public static int uperK(int[] a, int k){
int l = 0;
int r = a.length - 1;
//標注1: 這里是l<r,
while(l < r){
//標注2: 這樣的操作是為了取高位
int mid = (l + r + 1) / 2;
if(a[mid] <= k) { //標注3:因為a[mid]<=k,所以a[mid]可能=k,所以mid坐標也滿足條件,l = mid而不是mid+1;
l = mid;
}else{
r= mid - 1; //這是a[mid] > k的時候。
}
}
//標注4: 因為此時求得到的是最接近於目標值k的數,
// 如果最小值都大於k的話,那么就沒有辦法得到了,所以就進行一個判斷
if(a[l] > k) return -1;
//標注5: 其實這里無論返回 a[l] 還是a[r]都行,循環的退出時間是l == r 的時候
return a[l];
}
-
標注1解釋:
因為我們的目的是“通過縮小范圍,得到當l = r的時候,l 標記的值”的情況,所以直到 l< r條件被打破的時候的l就是我們要求的值。這跟上一道問題“查找指定值”是不一樣的 -
標注2解釋:
標注2: 這樣的操作是為了讓 mid 標志 取高位, 才能讓循環順序跳出來,舉個死循環的例子
注意這個例子采取的是mid = (l + r) / 2 取低位的情況,為了展示死循環的形成過程,我們原題的做法是取高位的
數據: int[] num = {1,2,3,4,5,6,7,8,9}, 查找小於或者等於8的最小值。
如上圖,此時左坐標是0, 右是8, 那么
mid = (0 + 8) / 2 = 4,num[mid] = num[4] <= k, ,向右找結果,所以有 l = mid,開始下一個循環。
此時的mid = 6, num[6] = 7;
num[mid] <= k, 向右找 l = 6
下一步:
可以得到 l = 6, r = 8, mid = (6+8) / 2 = 7,a[7] = 8 <= k,那么有l = mid = 7, r = 8,
推到得到mid = 7, 問題來了上一步的時候mid已經是7了,結果會使得mid一直是7,一直循環下去。
所以如果我們求的是二分法求小於或者等於k的最大值的話,我們mid 必須取得中值的上界,
- 標注3解釋:
因為a[mid]<=k,所以a[mid]可能=k,所以mid坐標也滿足條件,l = mid而不是mid+1; - 標注4解釋:
如果最小值都大於k的話,那么就沒有辦法得到了,所以就進行一個判斷
最典型的例子是:
{1,2,3,4,5,6,7,8} ,然后k是0的時候, 這樣得到的結果只能是數組中最接近與k的數,但是如果他還是大於k
你們可以算一下上面這個例子,到最后num[l] 是1, 永遠都大於0, 那么得返回取不到值,所以return -1; - 標注5解釋:
其實這里無論返回 a[l] 還是a[r]都行,循環的退出時間是l == r 的時候
二分查找大於或等於k的最小值
直接上代碼了,想必大家都清楚了,看了上面問題2的解釋
public static int downK(List list, int key){
while (low < high) {
//這里進行的是取低位, 也是為了使得循環可以正確退出,防止死循環
int mid = (low + high)/2;
if (a[mid] < key) {
low = mid +1;
} else { //a[mid] >= key
high = mid; //因為mid也滿足情況
}
}
//這里進行檢查的原因參考上面的標注
if (a[high] >= key) {
return high;
} else {
return -1;
}
}