轉載 二分查找關於邊界問題的坑點與總結

原文鏈接:https://blog.csdn.net/haolexiao/article/details/53541837

原文鏈接:https://blog.csdn.net/haolexiao/article/details/53541837

二分查找足夠簡單,但是坑點也有不少,下面來總結一下

以下是二分查找的標準寫法
以下這個函數是二分查找nums中[left,right)部分,right值取不到,如果查到的話,返回所在地,如果查不到則返回最後一個小於target值得後一個位置。

//右值點不能取到的情況
    int binary_search(vector<int>& nums,int left,int right, int target) { 
    //坑點(1)right究竟能不能取到的問題,這裏是不能取到的情況
        int i = left;
        int j= right;
        while(i<j){
            int mid = i+(j-i)/2;             //坑點(2)這裏儘量這麼寫,因爲如果寫成(i+j)/2則有溢出的風險
            if(nums[mid]>=target)        //坑點(3)這個地方大於還是大於等於要依據情況而定
                j = mid;            //坑點(4)因爲右值點反正不能取到,所以j就可以等於mid
            else
                i = mid+1;           //坑點(5)
        }
        return i;
    }

//右值點能取到的情況
    int searchInsert(vector<int>& nums,int left,int right, int target) {
        int i = left;
        int j= right;
        while(i<=j ){
            int mid = i+(j-i)/2;
            if(nums[mid]>=target)
                j = mid-1;
            else
                i = mid+1;
        }
        return i;
    }

一般來說寫的二分查找的標準程序應該是右邊right值取不到的情況,所以while循環中要加一步判斷i是否小於等於nums.size();

對於坑點2,如果是右值點可以取到情況下,必須是i<=j否則會出現查找偏差,比如數組中就一個元素[3],讓查找5的位置,理應返回1,但是此時會輸出成0,
對於坑點3,主要應用在如果數組中有重複元素,需要判斷其具體要返回什麼位置。
對於坑點,5,如果i不加1的,比如i = 0,j= 1 的情況,會出現mid = 0然後一直死循環下去
對於坑點4,依據right能不能取到而定,如果right可以取到則,right必須要-1,不減1的話,還是會出現i = j時的死循環。
另外一種二分查找,今天面試頭條的時候,被要求寫一個二分查找,原本以爲足夠的簡單,但是發現坑點還是有很多的,比如面試官問,如果數組中出現了重複元素該怎麼辦?怎麼返回重複元素的第一個位置,如果查不到則返回-1,後來想了想寫了個這個方法

int searchInsert(vector<int>& nums, int target) {
    int i = 0;
    int j = nums.size() - 1;
    while (i < j) {
        int mid = i + (j - i) / 2;
        if (nums[mid] > target)
            j = mid - 1;
        else
            if (nums[mid] == target)      //即繼續二分查找查找相等元素的左邊界即可
                j = mid;
            else
                i = mid + 1;
    }
    if (nums[i] == target)
        return i;
    else
        return -1;
}

這個有一個簡化的寫法,減少比較次數【注意以下寫法只適合返回最開始出現的位置,如果題目改成出現的最後的一個位置,則還是得用上面的方法】

int binarySearch(vector<int> &array, int target) {
       // write your code here
    int i = 0;
    int j = array.size() - 1;
    while (i < j) {
        int mid = i + (j - i) / 2;
        if (array[mid] >= target)
            j = mid ;
        else
            i = mid + 1;
    }
    if (array[i] == target)
        return i;
    else
        return -1;
 }int binarySearch(vector<int> &array, int target) {
       // write your code here
    int i = 0;
    int j = array.size() - 1;
    while (i < j) {
        int mid = i + (j - i) / 2;
        if (array[mid] >= target)
            j = mid ;
        else
            i = mid + 1;
    }
    if (array[i] == target)
        return i;
    else
        return -1;
 }

二分查找的變種
leetcode 153.Find Minimum in Rotated Sorted Array
就是一個排好序的數組,然後將其平移之後,找最小的元素。
諸如[3 4 5 6 7 1 2]

 int findMin(vector<int> &num) {
        int start=0,end=num.size()-1;

        while (start<end) {
            if (num[start]<num[end])
                return num[start];

            int mid = (start+end)/2;

            if (num[mid]>=num[start]) {  //這一步爲什麼要≥是因爲如果mid = start的情況
                start = mid+1;
            } else {
                end = mid;
            }
        }

        return num[start];
    }

//更簡潔的做法,比右邊,因爲最右邊的必然嚴格小於最左邊,所以等於上面的方法比了一個次小的
    int findMin(vector<int>& nums) {
        int i = 0, j = nums.size()-1;
        while(i<j){
            int mid = (i+j)/2;
            if(nums[mid]>nums[j])
                i =mid+1;
            else
                j = mid;
        }
        return nums[i];
    }

154. Find Minimum in Rotated Sorted Array II
它的進階形式,如果有重複元素的話,該如何呢
要考慮到這種情況[3 3 3 3 3 3 3 3 1 3 ] [1 1 1 1 1 1 1 1 1 1 2 1 1]

    int findMin(vector<int>& nums) {
        int i = 0, j = nums.size()-1;
        while(i<j){
            int mid = (i+j)/2;
            if(nums[mid]>nums[j])
                i =mid+1;
            else
                if(nums[mid]<nums[j])
                    j = mid;
                else
                    j--;
        }
        return nums[i];
    }

但實際上這種方法是有一點小問題的,雖然在這個問題中體現不出來,比如就是如果最小值是重複的,且最小值橫跨數組左右兩個端點的時候,諸如[1 1 1 1 1 1 1 1 1 1 2 1 1]這種情況,它找出的最小值的位置其實嚴格意義上不算最小值的位置。

33. Search in Rotated Sorted Array
就是在平移數組中找到一個目標數target,找不到則返回-1,找到返回下標

這道題一開始的思路是直接二分,然後,後來看了解答,就是用上面的方法找出最小值,然後最小值的位置也就意味着其右平移的距離,所以先假設數組沒有平移,二分查找,mid的位置給換成現在數組的位置 rot_mid = (mid+rotate)%n

    int search(vector<int>& nums, int target) {
        int i=0,j= nums.size()-1;
        while(i<j){
            int mid = (i+j)/2;
            if( nums[mid]>nums[j])
                i = mid+1;
            else
                j = mid;
        }
        int rot = i;
        i = 0;
        j = nums.size()-1;
        while(i<=j){   //注意這個地方必須是<=,因爲[1] 找1,如果是<的話就不行了。
            int mid = (i+j)/2;
            int rot_mid = (mid+rot)%nums.size();
            if(nums[rot_mid] == target) return rot_mid;
            if(nums[rot_mid]>target)
                j = mid-1;
            else
                i = mid+1;
        }
        return -1;
    }

進階形式,就是數組中有重複元素了怎麼辦
81. Search in Rotated Sorted Array II

    bool search(vector<int>& nums, int target) {
        int i = 0,j = nums.size()-1;
        while(i<j){
            int mid = (i+j)/2;
            if(nums[mid]>nums[j])
                i = mid+1;
            else
                if(nums[mid]<nums[j])
                    j = mid;
                else
                    j--;
        }
//鑑於[*]處的分析,所以這種最小值是重複的,且橫跨首尾的情況,需要額外處理
        if(i == 0 && nums[i] == nums[nums.size()-1]){
            i = nums.size()-1;
            while(i>0 &&nums[i-1] == nums[i]) i--;
        }

        int rot = i;
        i = 0;
        j = nums.size()-1;
        while(i<=j){
            int mid = (i+j)/2;
            int rot_mid = (mid+rot)%nums.size();
            if(nums[rot_mid] == target) return true;
            if(nums[rot_mid]>target)
                j = mid-1;
            else
                i = mid+1;
        }
        return false;
    }

或者採用我一開始的直接二分判斷的方法

    bool search(vector<int>& nums, int target) {
        int i = 0,j = nums.size()-1;
        while(i<=j){
            int mid = (i+j)/2;
            if(nums[mid] == target)
                return true;
            if(nums[mid]>nums[j])
                if(target>nums[mid] ||target<=nums[j])
                    i = mid+1;
                else
                    j = mid-1;
            else
                if(nums[mid]<nums[j])
                    if(target>nums[mid] && target<=nums[j])
                        i = mid+1;
                    else
                        j = mid-1;
                else
                    j--;
        }
        return false;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章