二分查找有這麼簡單?

題目一

給定一個有序(非降序)數組A,求任意一個i使得A[i]等於key,不存在則返回-1

這個是最原始的二分查找題目,利用數組的有序特性,拆半查找,使得查找時間複雜度爲O(logN)。

int search(int arr[], int n, int key)  
{  
    int low = 0, high = n-1;  
    while(low <= high)  
    {  
        // 注意:若使用(low+high)/2求中間位置容易溢出  
        int mid = low+((high-low)>>1);   
        if(A[mid] == key)  
            return mid;  
        else if(A[mid] < key)  
            low = mid+1;  
        else // A[mid] > key  
            high = mid-1;  
    }  
    return -1;  
}  

題目二

給定一個有序(非降序)數組A,可含有重複元素,求最小的i使得A[i]等於key,不存在則返回-1

此題也就是求key在數組中第一次出現的位置。這裏可能會有人想先直接用原始的二分查找,如果不存在直接返回-1,如果存在,然後再順序找到這個等於key值區間的最左位置,這樣的話,最壞情況下的複雜度就是O(n)了,沒有完全發揮出二分查找的優勢。這裏的解法具體過程請參考實現代碼與註釋。

int searchFirstPos(int arr[], int n, int key)  
{  
    if(n <= 0) return -1;  
    int low = 0, high = n-1;  
    while(low < high)  
    {  
        int mid = low+((high-low)>>1);  
        if(A[mid] < key)  
            low = mid+1;  
        else // A[mid] >= key  
            high = mid;  
    }  
    /*  
    循環過程中,當low大於0時,A[low-1]是小於key的,因爲A[mid] < key時,
    low=mid+1;當high小於n-1時,A[high]是大於等於key的,因爲A[mid] >= key時,
    high = mid;循環結束時,low 等於 high,所以,如果A[low](A[high])等於key,
    那麼low(high)就是key出現的最小位置,否則key在數組中不存在。
    */  
    if(A[low] != key)  
        return -1;  
    else  
        return low;  
}  


題目三

給定一個有序(非降序)數組A,可含有重複元素,求最大的i使得A[i]等於key,不存在則返回-1

此題也就是求key在數組中最後一次出現的位置。與上一題基本一樣,但是有個地方要注意,具體請參考實現代碼與註釋。

int searchLastPos(int arr[], int n, int key)  
{     
    if(n <= 0) return -1;  
    int low = 0, high = n-1;  
    while(low < high)  
    {  
        /*
        這裏中間位置的計算就不能用low+((high-low)>>1)了,因爲當low+1等於high
        且A[low] <= key時,會死循環;所以這裏要使用low+((high-low+1)>>1),
        這樣能夠保證循環會正常結束。
        */  
        int mid = low+((high-low+1)>>1);  
        if(A[mid] > key)  
            high = mid-1;  
        else // A[mid] <= key  
            low = mid;  
    }  
    /*  
    循環過程中,當high小於n-1時,A[high+1]是大於key的,因爲A[mid] > key時,
    high=mid-1;當low大於0時,A[low]是小於等於key的,因爲A[mid] <= key時,
    low = mid;循環結束時,low 等於 high,所以,如果A[high](A[low])等於key,
    那麼high(low)就是key出現的最大位置,否則key在數組中不存在。
    */  
    if(A[high] != key)  
        return -1;  
    else  
        return high;  
}  


題目四

給定一個有序(非降序)數組A,可含有重複元素,求最大的i使得A[i]小於key,不存在則返回-1

也就是求小於key的最大元素的位置。

int searchLastPosLessThan(int arr[], int n, int key)  
{  
    if(n <= 0) return -1;  
    int low = 0, high = n-1;  
    while(low < high)  
    {  
        int mid = low+((high-low+1)>>1); // 注意,不要導致死循環  
        if(A[mid] < key)  
            low = mid;  
        else // A[mid] >= key  
            high = mid-1;  
    }  
    /*  
    循環過程中,當low大於0時,A[low]是小於key的,因爲A[mid] < key時,
    low=mid;當high小於n-1時,A[high+1]是大於等於key的,因爲A[mid] >= key時,
    high = mid-1;循環結束時,low 等於 high,所以,如果A[low](A[high])小於key,
    那麼low(high)就是要找的位置,否則不存在這樣的位置(A[0] >= key時)。
    */  
    return A[low] < key ? low : -1;  
}

題目五

給定一個有序(非降序)數組A,可含有重複元素,求最小的i使得A[i]大於key,不存在則返回-1。

也就是求大於key的最小元素的位置。

int searchFirstPosGreaterThan(int arr[], int n, int key)  
{  
    if(n <= 0) return -1;  
    int low = 0, high = n-1;  
    while(low < high)  
    {  
        int mid = low+((high-low)>>1);  
        if(A[mid] > key)  
            high = mid;  
        else // A[mid] <= key  
            low = mid+1;  
    }  
    /*  
    循環過程中,當low大於0時,A[low-1]是小於等於key的,因爲A[mid] <= key時,
    low=mid+1;當high小於n-1時,A[high]是大於key的,因爲A[mid] > key時,
    high = mid;循環結束時,low 等於 high,所以,如果A[high](A[low])大於key,
    那麼high(low)就是要找的位置,否則不存在這樣的位置(A[n-1] <= key時)。
    */  
    return A[high] > key ? high : -1;  
}  


題目六

給定一個有序(非降序)數組A,可含有重複元素,求key在數組中出現的次數

求出第一次出現位置和最後一次出現位置。

int count(int arr[], int n, int key)  
{  
    int firstPos = searchFirstPos(A, n, key); // 第一次出現位置  
    if(firstPos == -1)  
        return 0;  
    int lastPos = searchLastPos(A, n, key);  // 最後一次出現位置  
    return lastPos-firstPos+1;  // 出現次數  
}  

題目七

給定一個有序(非降序)數組A,若key在數組中出現,返回位置,若不存在,返回它應該插入的位置

*如 [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 arr[], int n, int key) {  
    // 如果比最大值還大,那插入位置就是位置n  
    if(A[n-1] < key)   
        return n;  
    int low = 0, high = n-1;  
    while(low < high)  
    {  
        int mid = low+((high-low)>>1);  
        if(A[mid] >= key)  
            high = mid;  
        else // A[mid] < key  
            low = mid+1;  
    }  
    /*   
    循環過程中,當low大於0時,A[low-1]是小於key的,因爲A[mid] < key時,  
    low=mid+1;當high小於n-1時,A[high]是大於等於key的,因爲A[mid] >= key時,  
    high = mid;循環結束時,low 等於 high,所以,如果A[low](A[high])等於key,  
    那麼low(high)就是key出現的位置,否則low就是key在數組中應該插入的位置。  
    */  
    return high;  
}  

題目八

給定一個有序(非降序)數組A,可含有重複元素,求絕對值最小的元素的位置

找第一個大於等於0的位置,然後和前一個元素的絕對值比較,返回絕對值較小的元素的位置。

int searchMinAbs(int arr[], 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; 
}

題目九

給定一個有序(非降序)數組A和一個有序(非降序)數組B,可含有重複元素,求兩個數組合並結果中的第k(k>=0)個數字

這個題目出現了兩個數組,有序的,不管怎樣我們就應該首先考慮二分查找是否可行。若使用順序查找,時間複雜度最低爲O(k),就是類似歸併排序中的歸併過程。使用用二分查找時間複雜度爲O(logM+logN)。

int findKthIn2SortedArrays(int arr[], 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);  
}  

題目十

一個有序(升序)數組,沒有重複元素,在某一個位置發生了旋轉後,求key在變化後的數組中出現的位置,不存在則返回-1

*如 0 1 2 4 5 6 7 可能變成 4 5 6 7 0 1 2. 
我們先比較中間元素是否是目標值,如果是返回位置。如果不是,我們就應該想辦法將搜索區間減少一半。因爲存在旋轉變化,所以我們要多做一些判斷。我們知道因爲只有一次旋轉變化,所以中間元素兩邊的子數組肯定有一個是有序的,那麼我們可以判斷key是不是在這個有序的子數組中,從而決定是搜索這個子數組還是搜索另一個子數組。*

int searchInRotatedArray(int arr[], int n, int key)   
{  
    int low = 0, high = n-1;  
    while(low <= high)  
    {  
        int mid = low+((high-low)>>1);  
        if(A[mid] == key)   
            return mid;  
        if(A[mid] >= A[low])   
        {  
            // low ~ mid 是升序的  
            if(key >= A[low] && key < A[mid])  
                high = mid-1;  
            else  
                low = mid+1;  
        }  
        else  
        {  
            // mid ~ high 是升序的  
            if(key > A[mid] && key <= 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. **

題目十一


一個有序(升序)數組,沒有重複元素,在某一個位置發生了旋轉後,求最小值所在位置

如果中間元素小於左端元素,則最小值在左半區間內(包含中間元素);如果中間元素大於右端元素,則最小值在右半區間內(包含中間元素)。

int searchMinInRotatedArray(int arr[], 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;  
}  

題目十二


一個有序(升序)數組,沒有重複元素,在某一個位置發生了旋轉後,求第k(k > 0)小元素的位置

我們可以利用上一題的解答,求出最小值所在位置後,便可以求出第k小元素。

int searchKthInRotatedArray(int arr[], int n, int k)   
{  
    int posMin = searchMinInRotatedArray(A, n);  
    return (posMin+k-1)%n;  
}  

題目十三

查找數組中第一個比k大的數的下標

*當low和high都是非負數時,使用 mid = low + (high - low) / 2;這種形式可以避免溢出。 
當low和high一個爲負另一個爲非負時,用mid = (low + high) / 2;這種形式可以避免溢出。*

int searrrch_first_larger_k(int arr[], int length, int key)  
{  
    if (arr == nullptr || length <= 0 || arr[length - 1] <= key)  
        return -1;  
    int res = length - 1,low=0,high=length-1;  
    while (low <= high)
    {  

        int mid = low + (high - low) / 2;  
        if (arr[mid] > key)  
        {  
            res = mid;  
            high = mid - 1;  
        }  
        else// if (arr[mid] <= key)  
            low = mid + 1;  
    }  
    return res;  
}  

 

發佈了78 篇原創文章 · 獲贊 34 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章