二分查詢--你真的掌握了嗎?
版權所有,轉載請註明出處,謝謝!
http://blog.csdn.net/walkinginthewind/article/details/8937978
二分查詢,最基本的演算法之一,也是面試中常被考察的重點,因為基本的演算法最能反映出一個人的基礎是否紮實。本文對二分查詢相關題目做一個總結。
題目列表:
1. 給定一個有序(非降序)陣列A,求任意一個i使得A[i]等於target,不存在則返回-1
2. 給定一個有序(非降序)陣列A,可含有重複元素,求最小的i使得A[i]等於target,不存在則返回-1
3. 給定一個有序(非降序)陣列A,可含有重複元素,求最大的i使得A[i]等於target,不存在則返回-1
4. 給定一個有序(非降序)陣列A,可含有重複元素,求最大的i使得A[i]小於target,不存在則返回-1
5. 給定一個有序(非降序)陣列A,可含有重複元素,求最小的i使得A[i]大於target,不存在則返回-1
6. 給定一個有序(非降序)陣列A,可含有重複元素,求target在陣列中出現的次數。
7. 給定一個有序(非降序)陣列A,若target在陣列中出現,返回位置,若不存在,返回它應該插入的位置。
8. 給定一個有序(非降序)陣列A,可含有重複元素,求絕對值最小的元素的位置
9. 給定一個有序(非降序)陣列A和一個有序(非降序)陣列B,可含有重複元素,求兩個陣列合並結果中的第k(k>=0)個數字。
10. 一個有序(升序)陣列,沒有重複元素,在某一個位置發生了旋轉後,求target在變化後的陣列中出現的位置,不存在則返回-1.
11. 一個有序(升序)陣列,沒有重複元素,在某一個位置發生了旋轉後,求最小值所在位置
12. 一個有序(升序)陣列,沒有重複元素,在某一個位置發生了旋轉後,求第k(k > 0)小元素
1. 給定一個有序(非降序)陣列A,求任意一個i使得A[i]等於target,不存在則返回-1
這個是最原始的二分查詢題目,利用陣列的有序特性,拆半查詢,使得查詢時間複雜度為O(logN)。請參考實現程式碼與註釋。
- int search(int A[], int n, int target)
- {
- int low = 0, high = n-1;
- while(low <= high)
- {
- // 注意:若使用(low+high)/2求中間位置容易溢位
- int mid = low+((high-low)>>1);
- if(A[mid] == target)
- return mid;
- else if(A[mid] < target)
- low = mid+1;
- else // A[mid] > target
- high = mid-1;
- }
- return -1;
- }
int search(int A[], int n, int target)
{
int low = 0, high = n-1;
while(low <= high)
{
// 注意:若使用(low+high)/2求中間位置容易溢位
int mid = low+((high-low)>>1);
if(A[mid] == target)
return mid;
else if(A[mid] < target)
low = mid+1;
else // A[mid] > target
high = mid-1;
}
return -1;
}
2. 給定一個有序(非降序)陣列A,可含有重複元素,求最小的i使得A[i]等於target,不存在則返回-1此題也就是求target在陣列中第一次出現的位置。這裡可能會有人想先直接用原始的二分查詢,如果不存在直接返回-1,如果存在,然後再順序找到這個等於target值區間的最左位置,這樣的話,最壞情況下的複雜度就是O(n)了,沒有完全發揮出二分查詢的優勢。這裡的解法具體過程請參考實現程式碼與註釋。
- int searchFirstPos(int A[], int n, int target)
- {
- if(n <= 0) return -1;
- int low = 0, high = n-1;
- while(low < high)
- {
- int mid = low+((high-low)>>1);
- if(A[mid] < target)
- low = mid+1;
- else // A[mid] >= target
- high = mid;
- }
- /*
- 迴圈過程中,當low大於0時,A[low-1]是小於target的,因為A[mid] < target時,
- low=mid+1;當high小於n-1時,A[high]是大於等於target的,因為A[mid] >= target時,
- high = mid;迴圈結束時,low 等於 high,所以,如果A[low](A[high])等於target,
- 那麼low(high)就是target出現的最小位置,否則target在陣列中不存在。
- */
- if(A[low] != target)
- return -1;
- else
- return low;
- }
int searchFirstPos(int A[], int n, int target)
{
if(n <= 0) return -1;
int low = 0, high = n-1;
while(low < high)
{
int mid = low+((high-low)>>1);
if(A[mid] < target)
low = mid+1;
else // A[mid] >= target
high = mid;
}
/*
迴圈過程中,當low大於0時,A[low-1]是小於target的,因為A[mid] < target時,
low=mid+1;當high小於n-1時,A[high]是大於等於target的,因為A[mid] >= target時,
high = mid;迴圈結束時,low 等於 high,所以,如果A[low](A[high])等於target,
那麼low(high)就是target出現的最小位置,否則target在陣列中不存在。
*/
if(A[low] != target)
return -1;
else
return low;
}
3. 給定一個有序(非降序)陣列A,可含有重複元素,求最大的i使得A[i]等於target,不存在則返回-1
此題也就是求target在陣列中最後一次出現的位置。與上一題基本一樣,但是有個地方要注意,具體請參考實現程式碼與註釋。
- int searchLastPos(int A[], int n, int target)
- {
- if(n <= 0) return -1;
- int low = 0, high = n-1;
- while(low < high)
- {
- /*
- 這裡中間位置的計算就不能用low+((high-low)>>1)了,因為當low+1等於high
- 且A[low] <= target時,會死迴圈;所以這裡要使用low+((high-low+1)>>1),
- 這樣能夠保證迴圈會正常結束。
- */
- int mid = low+((high-low+1)>>1);
- if(A[mid] > target)
- high = mid-1;
- else // A[mid] <= target
- low = mid;
- }
- /*
- 迴圈過程中,當high小於n-1時,A[high+1]是大於target的,因為A[mid] > target時,
- high=mid-1;當low大於0時,A[low]是小於等於target的,因為A[mid] <= target時,
- low = mid;迴圈結束時,low 等於 high,所以,如果A[high](A[low])等於target,
- 那麼high(low)就是target出現的最大位置,否則target在陣列中不存在。
- */
- if(A[high] != target)
- return -1;
- else
- return high;
- }
int searchLastPos(int A[], int n, int target)
{
if(n <= 0) return -1;
int low = 0, high = n-1;
while(low < high)
{
/*
這裡中間位置的計算就不能用low+((high-low)>>1)了,因為當low+1等於high
且A[low] <= target時,會死迴圈;所以這裡要使用low+((high-low+1)>>1),
這樣能夠保證迴圈會正常結束。
*/
int mid = low+((high-low+1)>>1);
if(A[mid] > target)
high = mid-1;
else // A[mid] <= target
low = mid;
}
/*
迴圈過程中,當high小於n-1時,A[high+1]是大於target的,因為A[mid] > target時,
high=mid-1;當low大於0時,A[low]是小於等於target的,因為A[mid] <= target時,
low = mid;迴圈結束時,low 等於 high,所以,如果A[high](A[low])等於target,
那麼high(low)就是target出現的最大位置,否則target在陣列中不存在。
*/
if(A[high] != target)
return -1;
else
return high;
}
4. 給定一個有序(非降序)陣列A,可含有重複元素,求最大的i使得A[i]小於target,不存在則返回-1。
也就是求小於target的最大元素的位置。請參考實現程式碼與註釋。
- int searchLastPosLessThan(int A[], int n, int target)
- {
- if(n <= 0) return -1;
- int low = 0, high = n-1;
- while(low < high)
- {
- int mid = low+((high-low+1)>>1); // 注意,不要導致死迴圈
- if(A[mid] < target)
- low = mid;
- else // A[mid] >= target
- high = mid-1;
- }
- /*
- 迴圈過程中,當low大於0時,A[low]是小於target的,因為A[mid] < target時,
- low=mid;當high小於n-1時,A[high+1]是大於等於target的,因為A[mid] >= target時,
- high = mid-1;迴圈結束時,low 等於 high,所以,如果A[low](A[high])小於target,
- 那麼low(high)就是要找的位置,否則不存在這樣的位置(A[0] >= target時)。
- */
- return A[low] < target ? low : -1;
- }
int searchLastPosLessThan(int A[], int n, int target)
{
if(n <= 0) return -1;
int low = 0, high = n-1;
while(low < high)
{
int mid = low+((high-low+1)>>1); // 注意,不要導致死迴圈
if(A[mid] < target)
low = mid;
else // A[mid] >= target
high = mid-1;
}
/*
迴圈過程中,當low大於0時,A[low]是小於target的,因為A[mid] < target時,
low=mid;當high小於n-1時,A[high+1]是大於等於target的,因為A[mid] >= target時,
high = mid-1;迴圈結束時,low 等於 high,所以,如果A[low](A[high])小於target,
那麼low(high)就是要找的位置,否則不存在這樣的位置(A[0] >= target時)。
*/
return A[low] < target ? low : -1;
}
5. 給定一個有序(非降序)陣列A,可含有重複元素,求最小的i使得A[i]大於target,不存在則返回-1。也就是求大於target的最小元素的位置。請參考實現程式碼與註釋。
- int searchFirstPosGreaterThan(int A[], int n, int target)
- {
- if(n <= 0) return -1;
- int low = 0, high = n-1;
- while(low < high)
- {
- int mid = low+((high-low)>>1);
- if(A[mid] > target)
- high = mid;
- else // A[mid] <= target
- low = mid+1;
- }
- /*
- 迴圈過程中,當low大於0時,A[low-1]是小於等於target的,因為A[mid] <= target時,
- low=mid+1;當high小於n-1時,A[high]是大於target的,因為A[mid] > target時,
- high = mid;迴圈結束時,low 等於 high,所以,如果A[high](A[low])大於target,
- 那麼high(low)就是要找的位置,否則不存在這樣的位置(A[n-1] <= target時)。
- */
- return A[high] > target ? high : -1;
- }
int searchFirstPosGreaterThan(int A[], int n, int target)
{
if(n <= 0) return -1;
int low = 0, high = n-1;
while(low < high)
{
int mid = low+((high-low)>>1);
if(A[mid] > target)
high = mid;
else // A[mid] <= target
low = mid+1;
}
/*
迴圈過程中,當low大於0時,A[low-1]是小於等於target的,因為A[mid] <= target時,
low=mid+1;當high小於n-1時,A[high]是大於target的,因為A[mid] > target時,
high = mid;迴圈結束時,low 等於 high,所以,如果A[high](A[low])大於target,
那麼high(low)就是要找的位置,否則不存在這樣的位置(A[n-1] <= target時)。
*/
return A[high] > target ? high : -1;
}
6. 給定一個有序(非降序)陣列A,可含有重複元素,求target在陣列中出現的次數
求出第一次出現位置和最後一次出現位置。由於前面都已實現,這裡不多解釋。請參考實現程式碼與註釋
- int count(int A[], int n, int target)
- {
- int firstPos = searchFirstPos(A, n, target); // 第一次出現位置
- if(firstPos == -1)
- return 0;
- int lastPos = searchLastPos(A, n, target); // 最後一次出現位置
- return lastPos-firstPos+1; // 出現次數
- }
int count(int A[], int n, int target)
{
int firstPos = searchFirstPos(A, n, target); // 第一次出現位置
if(firstPos == -1)
return 0;
int lastPos = searchLastPos(A, n, target); // 最後一次出現位置
return lastPos-firstPos+1; // 出現次數
}
此題描述或者改為求目標值在陣列中的索引範圍。
7. 給定一個有序(非降序)陣列A,若target在陣列中出現,返回位置,若不存在,返回它應該插入的位置
如 [1,3,5,6], 5 → 2
[1,3,5,6], 2 → 1
[1,3,5,6], 7 → 4
[1,3,5,6], 0 → 0
- int searchInsert(int A[], int n, int target) {
- // 如果比最大值還大,那插入位置就是位置n
- if(A[n-1] < target)
- return n;
- int low = 0, high = n-1;
- while(low < high)
- {
- int mid = low+((high-low)>>1);
- if(A[mid] >= target)
- high = mid;
- else // A[mid] < target
- low = mid+1;
- }
- /*
- 迴圈過程中,當low大於0時,A[low-1]是小於target的,因為A[mid] < target時,
- low=mid+1;當high小於n-1時,A[high]是大於等於target的,因為A[mid] >= target時,
- high = mid;迴圈結束時,low 等於 high,所以,如果A[low](A[high])等於target,
- 那麼low(high)就是target出現的位置,否則low就是target在陣列中應該插入的位置。
- */
- return high;
- }
int searchInsert(int A[], int n, int target) {
// 如果比最大值還大,那插入位置就是位置n
if(A[n-1] < target)
return n;
int low = 0, high = n-1;
while(low < high)
{
int mid = low+((high-low)>>1);
if(A[mid] >= target)
high = mid;
else // A[mid] < target
low = mid+1;
}
/*
迴圈過程中,當low大於0時,A[low-1]是小於target的,因為A[mid] < target時,
low=mid+1;當high小於n-1時,A[high]是大於等於target的,因為A[mid] >= target時,
high = mid;迴圈結束時,low 等於 high,所以,如果A[low](A[high])等於target,
那麼low(high)就是target出現的位置,否則low就是target在陣列中應該插入的位置。
*/
return high;
}
8. 給定一個有序(非降序)陣列A,可含有重複元素,求絕對值最小的元素的位置
找第一個大於等於0的位置,然後和前一個元素的絕對值比較,返回絕對值較小的元素的位置。請參考實現程式碼與註釋
- int searchMinAbs(int A[], int n)
- {
- int low = 0, high = n-1;
- while(low < high)
- {
- int mid = low+((high-low)>>1);
- if(A[mid] < 0)
- low = mid+1;
- else // A[mid] >= 0
- high = mid;
- }
- /* 迴圈結束時,如果low != n-1,A[low] >= 0,如果low>0,A[low-1] < 0 */
- if(low > 0 && abs(A[low-1]) < abs(A[low]))
- return low-1;
- else
- return low;
- }
int searchMinAbs(int A[], int n)
{
int low = 0, high = n-1;
while(low < high)
{
int mid = low+((high-low)>>1);
if(A[mid] < 0)
low = mid+1;
else // A[mid] >= 0
high = mid;
}
/* 迴圈結束時,如果low != n-1,A[low] >= 0,如果low>0,A[low-1] < 0 */
if(low > 0 && abs(A[low-1]) < abs(A[low]))
return low-1;
else
return low;
}
9. 給定一個有序(非降序)陣列A和一個有序(非降序)陣列B,可含有重複元素,求兩個陣列合並結果中的第k(k>=0)個數字。
這個題目出現了兩個陣列,有序的,不管怎樣我們就應該首先考慮二分查詢是否可行。若使用順序查詢,時間複雜度最低為O(k),就是類似歸併排序中的歸併過程。使用用二分查詢時間複雜度為O(logM+logN)。二分查詢的具體實現過程請參考實現程式碼與註釋。
- int findKthIn2SortedArrays(int A[], int m, int B[], int n, int k)
- {
- if(m <= 0) // 陣列A中沒有元素,直接在B中找第k個元素
- return B[k];
- if(n <= 0) // 陣列B中沒有元素,直接在A中找第k個元素
- return A[k];
- int i = (m-1)>>1; // 陣列A的中間位置
- int j = (n-1)>>1; // 陣列B的中間位置
- if(A[i] <= B[j]) // 陣列A的中間元素小於等於陣列B的中間元素
- {
- /*
- 設x為陣列A和陣列B中小於B[j]的元素數目,則i+1+j+1小於等於x,
- 因為A[i+1]到A[m-1]中還可能存在小於等於B[j]的元素;
- 如果k小於i+1+j+1,那麼要查詢的第k個元素肯定小於等於B[j],
- 因為x大於等於i+1+j+1;既然第k個元素小於等於B[j],那麼只
- 需要在A[0]~A[m-1]和B[0]~B[j]中查詢第k個元素即可,遞迴呼叫下去。
- */
- if(k < i+1+j+1)
- {
- if(j > 0)
- return findKthIn2SortedArrays(A, m, B, j+1, k);
- else // j == 0時特殊處理,防止死迴圈
- {
- if(k == 0)
- return min(A[0], B[0]);
- if(k == m)
- return max(A[m-1], B[0]);
- return A[k] < B[0] ? A[k] : max(A[k-1], B[0]);
- }
- }
- /*
- 設y為陣列A和陣列B中小於於等於A[i]的元素數目,則i+1+j+1大於等於y;
- 如果k大於等於i+1+j+1,那麼要查詢到第k個元素肯定大於A[i],因為
- i+1+j+1大於等於y;既然第k個元素大於A[i],那麼只需要在A[i+1]~A[m-1]
- 和B[0]~B[n-1]中查詢第k-i-1個元素,遞迴呼叫下去。
- */
- else
- return findKthIn2SortedArrays(A+i+1, m-i-1, B, n, k-i-1);
- }
- // 如果陣列A的中間元素大於陣列B的中間元素,那麼交換陣列A和B,重新呼叫即可
- else
- return findKthIn2SortedArrays(B, n, A, m, k);
- }
int findKthIn2SortedArrays(int A[], int m, int B[], int n, int k)
{
if(m <= 0) // 陣列A中沒有元素,直接在B中找第k個元素
return B[k];
if(n <= 0) // 陣列B中沒有元素,直接在A中找第k個元素
return A[k];
int i = (m-1)>>1; // 陣列A的中間位置
int j = (n-1)>>1; // 陣列B的中間位置
if(A[i] <= B[j]) // 陣列A的中間元素小於等於陣列B的中間元素
{
/*
設x為陣列A和陣列B中小於B[j]的元素數目,則i+1+j+1小於等於x,
因為A[i+1]到A[m-1]中還可能存在小於等於B[j]的元素;
如果k小於i+1+j+1,那麼要查詢的第k個元素肯定小於等於B[j],
因為x大於等於i+1+j+1;既然第k個元素小於等於B[j],那麼只
需要在A[0]~A[m-1]和B[0]~B[j]中查詢第k個元素即可,遞迴呼叫下去。
*/
if(k < i+1+j+1)
{
if(j > 0)
return findKthIn2SortedArrays(A, m, B, j+1, k);
else // j == 0時特殊處理,防止死迴圈
{
if(k == 0)
return min(A[0], B[0]);
if(k == m)
return max(A[m-1], B[0]);
return A[k] < B[0] ? A[k] : max(A[k-1], B[0]);
}
}
/*
設y為陣列A和陣列B中小於於等於A[i]的元素數目,則i+1+j+1大於等於y;
如果k大於等於i+1+j+1,那麼要查詢到第k個元素肯定大於A[i],因為
i+1+j+1大於等於y;既然第k個元素大於A[i],那麼只需要在A[i+1]~A[m-1]
和B[0]~B[n-1]中查詢第k-i-1個元素,遞迴呼叫下去。
*/
else
return findKthIn2SortedArrays(A+i+1, m-i-1, B, n, k-i-1);
}
// 如果陣列A的中間元素大於陣列B的中間元素,那麼交換陣列A和B,重新呼叫即可
else
return findKthIn2SortedArrays(B, n, A, m, k);
}
10. 一個有序(升序)陣列,沒有重複元素,在某一個位置發生了旋轉後,求target在變化後的陣列中出現的位置,不存在則返回-1
如 0
1 2 4 5 6 7
可能變成 4
5 6 7 0 1 2
- int searchInRotatedArray(int A[], int n, int target)
- {
- int low = 0, high = n-1;
- while(low <= high)
- {
- int mid = low+((high-low)>>1);
- if(A[mid] == target)
- return mid;
- if(A[mid] >= A[low])
- {
- // low ~ mid 是升序的
- if(target >= A[low] && target < A[mid])
- high = mid-1;
- else
- low = mid+1;
- }
- else
- {
- // mid ~ high 是升序的
- if(target > A[mid] && target <= A[high])
- low = mid+1;
- else
- high = mid-1;
- }
- }
- return -1;
- }
int searchInRotatedArray(int A[], int n, int target)
{
int low = 0, high = n-1;
while(low <= high)
{
int mid = low+((high-low)>>1);
if(A[mid] == target)
return mid;
if(A[mid] >= A[low])
{
// low ~ mid 是升序的
if(target >= A[low] && target < A[mid])
high = mid-1;
else
low = mid+1;
}
else
{
// mid ~ high 是升序的
if(target > A[mid] && target <= A[high])
low = mid+1;
else
high = mid-1;
}
}
return -1;
}
如果這樣的陣列中存在重複元素,還能使用二分嗎?答案是不能。請看幾個例子
[1, 2, 2, 2, 2], [2, 1, 2, 2, 2], [2, 2, 1, 2, 2], [2, 2, 2, 1, 2], [2, 2, 2, 2, 1]這些都是有第一個陣列旋轉一次變化來的,我們不能通過二分確定是否存在元素1.
11. 一個有序(升序)陣列,沒有重複元素,在某一個位置發生了旋轉後,求最小值所在位置
如果中間元素小於左端元素,則最小值在左半區間內(包含中間元素);如果中間元素大於右端元素,則最小值在右半區間內(包含中間元素)。請參考實現程式碼與註釋。
- int searchMinInRotatedArray(int A[], int n)
- {
- if(n == 1)
- return 0;
- int low = 0, high = n-1;
- while(low < high-1) // 保證mid != low且mid != high
- {
- int mid = low+((high-low)>>1);
- if(A[mid] < A[low]) // 最小值在low~mid
- high = mid;
- else // A[mid] > A[low], // 最小值在mid和high之間
- low = mid;
- }
- return A[low] < A[low+1] ? low : low+1;
- }
int searchMinInRotatedArray(int A[], int n)
{
if(n == 1)
return 0;
int low = 0, high = n-1;
while(low < high-1) // 保證mid != low且mid != high
{
int mid = low+((high-low)>>1);
if(A[mid] < A[low]) // 最小值在low~mid
high = mid;
else // A[mid] > A[low], // 最小值在mid和high之間
low = mid;
}
return A[low] < A[low+1] ? low : low+1;
}
12. 一個有序(升序)陣列,沒有重複元素,在某一個位置發生了旋轉後,求第k(k > 0)小元素的位置我們可以利用上一題的解答,求出最小值所在位置後,便可以求出第k小元素。請參考實現程式碼與註釋
- int searchKthInRotatedArray(int A[], int n, int k)
- {
- int posMin = searchMinInRotatedArray(A, n);
- return (posMin+k-1)%n;
- }
int searchKthInRotatedArray(int A[], int n, int k)
{
int posMin = searchMinInRotatedArray(A, n);
return (posMin+k-1)%n;
}
相關文章
- 二分查詢,你真的掌握了嗎?
- 你真的會用二分查詢嗎?
- 分頁查詢,你真的懂嗎?
- 你真的完全掌握了promise麼?Promise
- java web開發這些細節你真的掌握了嗎JavaWeb
- 8道經典JavaScript面試題解析,你真的掌握JavaScript了嗎?JavaScript面試題
- 你真的理解 getLocationInWindow 了嗎?
- 你真的理解 new 了嗎?
- 堆排序你真的學會了嗎?排序
- 你真的已經搞懂JavaScript了嗎?JavaScript
- 查詢——二分查詢
- 設計模式之禪讀書筆記-單例模式你真的掌握了嗎?設計模式筆記單例
- 二分只能用來查詢元素嗎?
- 你真的理解JS的繼承了嗎?JS繼承
- async await 你真的用對了嗎?AI
- Myabtis動態SQL,你真的會了嗎?SQL
- 二分查詢(一)——純粹的二分查詢
- Python查詢-二分查詢Python
- 二分查詢
- 高併發,你真的理解透徹了嗎?
- Java面試必問面試題,你掌握了嗎?Java面試題
- 你真的理解this嗎
- 查詢演算法__二分查詢演算法
- 順序查詢和二分查詢
- 你真的知道什麼是“遊戲障礙”了嗎?遊戲
- 關於python編碼,你真的明白了嗎Python
- 實現註冊登入你真的會了嗎
- PHP二分查詢PHP
- 二分查詢法
- 企業網站建設技巧你掌握了嗎?網站
- 查詢演算法之二分查詢演算法
- 二分查詢 | 二分查詢的一種推薦寫法
- 【webpack進階】你真的掌握了loader麼?- loader十問Web
- 你真的理解setState嗎?
- 你真的理解==和===嗎
- WebView你真的熟悉嗎?WebView
- 你真的知道JS嗎JS
- 提高App的啟動速度,你真的做對了嗎?APP