【二分查詢】在排序陣列中查詢數字
阿新 • • 發佈:2018-12-16
排序數組裡很多二分查詢的題目,不能把排序這個性質浪費了。
面試題53-1:數字在排序陣列中出現的次數
統計一個數字在排序陣列中出現的次數。例如輸入排序陣列{1, 2, 3, 3, 3, 3, 4, 5}和數字3,由於3在這個陣列中出現了4次,因此輸出4。
二分查詢第一個k和最後一個k,計算它們的距離再+1。
#include<bits/stdc++.h>
using namespace std;
// 引數:
// data: 一個升序有序的整數陣列
// length: 陣列的長度
// k: 要尋找的數字
// start: (子)陣列起始位置下標
// end: (子)陣列結束位置下標
//找到陣列中第一個k的下標,如果陣列中不存在k,返回-1
int GetFirstK(const int* data, int length, int k, int start, int end) {
if(start > end)
return -1;
int middleIndex = (start + end) / 2;//二分查詢,分割點
int middleData = data[middleIndex];//分割點處的值
if(middleData == k) {//分割點處值是k
//分割點不是0,那麼它前面一個數字如果不是k,它就是第一個k
//分割點是0即一定是第一個k
if((middleIndex > 0 && data[middleIndex - 1] != k)
|| middleIndex == 0)
return middleIndex;//找到了第一個k
else//它不是第一個k,第一個k一定在它左邊
end = middleIndex - 1;
} else if(middleData > k)//分割點處值比k大
end = middleIndex - 1 ;//升序陣列,k海如果存在一定在左邊
else//分割點處比k小
start = middleIndex + 1;//升序陣列,k海如果存在一定在右邊
//執行至此說明還沒找到第一個k,遞迴呼叫二分查詢
return GetFirstK(data, length, k, start, end);
}
//找到陣列中最後一個k的下標,如果陣列中不存在k,返回-1
//GetLastK和GetFirstK的思路一樣
int GetLastK(const int* data, int length, int k, int start, int end) {
if(start > end)
return -1;
int middleIndex = (start + end) / 2;
int middleData = data[middleIndex];
if(middleData == k) {//分割點處值是k
//分割點不是length-1(最後一個點),那麼它後面的點要不是k它就是最後一個
//分割點是length-1,後面已經沒了,它一定是最後一個k
if((middleIndex < length - 1 && data[middleIndex + 1] != k)
|| middleIndex == length - 1)
return middleIndex;
else
start = middleIndex + 1;
} else if(middleData < k)
start = middleIndex + 1;
else
end = middleIndex - 1;
return GetLastK(data, length, k, start, end);
}
//在長為length的排序data陣列中計算k出現的次數
int GetNumberOfK(const int* data, int length, int k) {
int number = 0;//找不到就是0個
if(data != nullptr && length > 0) {//輸入合法性檢查
//找第一個k的下標和最後一個k的下標
int first = GetFirstK(data, length, k, 0, length - 1);
int last = GetLastK(data, length, k, 0, length - 1);
//大於-1即證實k的存在性,實際上這兩個條件只要有一個滿足另一個一定滿足
//因為只要"第一個k"存在,則"最後一個k"一定存在
if(first > -1 && last > -1)
number = last - first + 1;//兩個k之間全是k,因為陣列是排序陣列
}
return number;
}
int main() {
int data[] = {1, 2, 3, 3, 3, 3, 4, 5};
cout<<GetNumberOfK(data,sizeof(data)/sizeof(int),3)<<endl;//4
return 0;
}
面試題53-2:0~n-1中缺失的數字
一個長度為n-1的遞增排序陣列中的所有數字都是唯一的,並且每個數字都在範圍0到n-1之內。在範圍0到n-1的n個數字中有且只有一個數字不在該陣列中,請找出這個數字。
比如n=8,0~7缺了4,本來應該是: 現在變成: 可以看到前面的那部分下標和數字相等,後面的部分數字比下標大了1,第一個數字比下標大的數字,其所在位置就是缺的那個數字本來應該在的位置。
#include<bits/stdc++.h>
using namespace std;
//陣列長為length的升序數字陣列numbers,返回缺失的數字
//這裡應該注意length就是題目中的n-1
//因為n個數字缺了1個只剩n-1個,實際讀入就讀了n-1個而不是讀了n個
int GetMissingNumber(const int* numbers, int length) {
//輸入合法性檢查
if(numbers == nullptr || length <= 0)
return -1;
int left = 0;//二分查詢左位置
int right = length - 1;//二分查詢右位置
while(left <= right) {//沒找到時左>右
int middle = (right + left) >> 1;//二分中點
if(numbers[middle] != middle) {//如果和下標不同
//下標是0,就缺0
//下標不是0,看左邊一個數是不是和它的下標相等
if(middle == 0 || numbers[middle - 1] == middle - 1)
return middle;//如果是,當前下標就是缺的那個數
right = middle - 1;//否則,往左找
} else//中點和下標相同
left = middle + 1;//往右找
}
//特別注意,當缺的那個數就是n-1的時候,上面的迴圈找不到
//一直往右找,最後找到left=length超過right=length-1結束迴圈
if(left == length)
//特別注意length就是n-1,length-1可就是n-2了
return length;
//無效的輸入,比如陣列不是按要求排序的
//或者有數字不在0到n-1範圍之內
return -1;
}
int main() {
int numbers[]={0,1,2,3,5,6,7};
cout<<GetMissingNumber(numbers,7)<<endl;//4
return 0;
}
面試題53-3:陣列中數值和下標相等的元素
假設一個單調遞增的數組裡的每個元素都是整數並且是唯一的。請程式設計實現一個函式找出陣列中任意一個數值等於其下標的元素。例如,在陣列{-3, -1, 1, 3, 5}中,數字3和它的下標相等。
左邊的數字都比下標小或和下標相等,右邊的數字都比下標大或和下標相等。因為是找任意一個,所以這兩個條件只要設定成"小"和"大"就行了。
#include<bits/stdc++.h>
using namespace std;
//尋找長度為length的升序陣列numbers中某個和下標相等的元素
int GetNumberSameAsIndex(const int* numbers, int length) {
if(numbers == nullptr || length <= 0)//輸入合法性
return -1;
//二分左右點
int left = 0;
int right = length - 1;
//查詢
while(left <= right) {
//二分劃分點,其實就是(right+left)>>2
//我覺得下面作者這種寫法的優勢就是能避免上面的right+left越界
int middle = left + ((right - left) >> 1);
//中點和下標相等
if(numbers[middle] == middle)
return middle;//找到
//中點值比下標大
if(numbers[middle] > middle)
right = middle - 1;//往左找
else//比下標小
left = middle + 1;//往右找
}
return -1;//沒找到
}
int main() {
int numbers[] = { -3, -1, 1, 3, 5 };
cout<<GetNumberSameAsIndex(numbers,sizeof(numbers)/sizeof(int))<<endl;//3
return 0;
}
思考,如果是要找第一個(最後一個)和下標相等的元素呢?就和前面兩個題比較像了,判斷條件裡要同時判斷一下前面(後面)的一個元素,以及是不是第一個(最後一個)元素。