【總結】二分查找

目錄

二分法的原理

1.二分查找(模板1)

2.二分查找(模板2)

例題

1.x的平方根(leetcode 69)

2.Pow(x,n)(leetcode 50)

3.山脈數組的峯頂索引(leetcode 852)

4.山脈數組中查找目標值(leetcode 1095)

5.有序數組中的單一元素 (leetcode 540)


  • 二分法的原理

二分查找,又叫做折半查找,是一種在有序數組中查找某一特定元素的搜索算法。

1.二分查找(模板1)

這是我們經常見到的二分查找算法,主要作用是在循環體中查找某一元素,主要存在三個分支,其中兩個分支用於邊界的搜索。

Binary search into array.png

int Binary_search(int arr[],int n,int target)
{
    //在[l,r]閉區間搜索target
	int l=0,r=n-1; 
	while(l<=r)//當l==r時,區間依然有效 
	{
		int mid=l+(r-l)/2;
		if(arr[mid]==target)
			return mid+1;	
		else if(arr[mid]<target)
			l=mid+1;//在[mid+1,r]閉區間搜索target	
		else
			r=mid-1;//在[l,mid-1]閉區間搜索target
	}
	return -1;
 }

2.二分查找(模板2)

這種二分查找,主要是在循環體內縮小搜索區域,當退出循環以後區間只剩下一個元素,視情況單獨判斷。

int search(int nums[],int left,int right,int target)
{
	//在[left,right]裏查找target
	while(left<right)
	{
		//選取中位數時下取整 
		int mid=left+(right-left)/2;
		if(check(mid))
		{
			//下輪搜索區間[mid+1,rihjt]
			left=mid+1; 
		}
		else
		{
			//下輪搜索區間[left,mid]
			right=mid;
		}
	 }
	 //視情況而定,判斷left或者right是否符合題意。 
 } 

int search(int nums[],int left,int right,int target)
{
	//在[left,right]裏查找target
	while(left<right)
	{
		//選取中位數時上取整 
		int mid=left+(right-left+1)/2;
		if(check(mid))
		{
			//下輪搜索區間[left,mid-1]
			right=mid-1; 
		}
		else
		{
			//下輪搜索區間[mid,right]
			left=mid;
		}
	 }
	 //視情況而定,判斷left或者right是否符合題意。 
 } 
  • 例題

1.x的平方根(leetcode 69

  • 題目描述:

實現 int sqrt(int x) 函數。

計算並返回 x 的平方根,其中 x 是非負整數。

由於返回類型是整數,結果只保留整數的部分,小數部分將被捨去。

示例 1:

輸入: 4
輸出: 2

示例 2:

輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842..., 
     由於返回類型是整數,小數部分將被捨去。

  • 分析:

首先,我們知道一個非負整數 x 的平方根在[ 0 ,x]之間,而且這個區間是有序的。那麼我們就可以使用二分查找法。

  1. 如果中間元素 mid 等於x的平方根,那麼直接返回。
  2. 如果x/mid的值大於mid,說明x的平方根在mid的右邊,則l=mid+1;
  3. 如果x/mid的值小於mid,說明x的平方根在mid的左邊,則r=mid-1;
  4. 如果沒找到合適的值,則返回向下取整的值min(l,r),其實就是r,因爲當不滿足l<=r時,r<l;
class Solution {
public:
    int x_sqrt(int x)
    {
        if(x<2)
            return x;
        int l=2;
        int r=x/2;
        while(l<=r)
        {
            int mid=l+(r-l)/2;
            if(x/mid==mid)
                return mid;
            else if(x/mid>mid)
                l=mid+1;
            else
                r=mid-1;
        }
        return r;
    }
    int mySqrt(int x) {
        return x_sqrt(x);
    }
};

2.Pow(x,n)(leetcode 50

  •  題目描述:

實現 pow(x, n) ,即計算 x 的 n 次冪函數。

示例 1:

輸入: 2.00000, 10
輸出: 1024.00000


示例 2:

輸入: 2.10000, 3
輸出: 9.26100


示例 3:

輸入: 2.00000, -2
輸出: 0.25000
解釋: 2-2 = 1/22 = 1/4 = 0.25


說明:

-100.0 < x < 100.0
n 是 32 位有符號整數,其數值範圍是 [−231, 231 − 1] 。

  • 分析:

這道題計算x的平方,我們可以將x進行分組,分成一等分,一等分的,然後在將每一等分合並起來,組成最後的結果,這裏有點類似於歸併排序的過程。

    double powN(double x,long long l,long long r)
    {
        if(l>=r)
            return x;
        long long mid=l+(r-l)/2; 
        double left=powN(x,l,mid);
        double right=powN(x,mid+1,r);
        return left*right;
    }

注:這是我自己的實現,但是在最後會超時。 

    double powHelper(double x,long long N)
    {
        if(N==0)
            return 1.0;
        double y=powHelper(x,N/2);
        return N%2==0?y*y:y*y*x;
    }
class Solution {
public:
    double powN(double x,long long l,long long r)
    {
        if(l>=r)
            return x;
        long long mid=l+(r-l)/2; 
        double left=powN(x,l,mid);
        double right=powN(x,mid+1,r);
        return left*right;
    }
    double powHelper(double x,long long N)
    {
        if(N==0)
            return 1.0;
        double y=powHelper(x,N/2);
        return N%2==0?y*y:y*y*x;
    }
    double myPow(double x, int n) {
        long long N=n;
        if(N==0)
            return 1;
        else if(N<0)
            // return 1.0/powN(x,1,N*-1);
            return 1.0/powHelper(x,N*-1);
        else   
            // return powN(x,1,N);
            return powHelper(x,N);
    }
};

3.山脈數組的峯頂索引(leetcode 852

  • 題目描述:

我們把符合下列屬性的數組 A 稱作山脈:

A.length >= 3,存在 0 < i < A.length - 1 使得A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1]
給定一個確定爲山脈的數組,返回任何滿足 A[0] < A[1] < ... A[i-1] < A[i] > A[i+1] > ... > A[A.length - 1] 的 i 的值。

示例 1:

輸入:[0,1,0]
輸出:1

示例 2:

輸入:[0,2,1,0]
輸出:1
 

提示:

3 <= A.length <= 10000
0 <= A[i] <= 10^6
A 是如上定義的山脈

  • 分析:

 我們需要找到山峯,可以通過線性查找,當A[i]>A[i+1]時,結束遍歷,這樣的時間複雜度爲O(n),爲了能夠降低時間複雜度,我們可以使用二分查找。

  • 當A[mid]<A[mid+1]時,處於山峯的左邊,索引 mid 一定不是峯頂的位置,搜索峯頂的位置在 mid 的右邊(不包括i);
  • 當A[mid]>A[mid+1]時,處於山峯的右邊,索引 mid 可能是峯頂的位置,搜索峯頂的位置在 mid 的左邊(包括i);
class Solution {
public:
    int peakIndexInMountainArray(vector<int>& A) {
        int l=0;
        int r=A.size()-1;
        while(l<r)
        {
            int mid=l+(r-l)/2;
            if(A[mid]>A[mid+1])
                r=mid;
            else
                l=mid+1;
        }
        return l;
    }
};

4.山脈數組中查找目標值(leetcode 1095

  • 題目描述

(這是一個 交互式問題 )

給你一個 山脈數組 mountainArr,請你返回能夠使得 mountainArr.get(index) 等於 target 最小 的下標 index 值。

如果不存在這樣的下標 index,就請返回 -1。

何爲山脈數組?如果數組 A 是一個山脈數組的話,那它滿足如下條件:

首先,A.length >= 3

其次,在 0 < i < A.length - 1 條件下,存在 i 使得:

A[0] < A[1] < ... A[i-1] < A[i]
A[i] > A[i+1] > ... > A[A.length - 1]

你將 不能直接訪問該山脈數組,必須通過 MountainArray 接口來獲取數據:

MountainArray.get(k) - 會返回數組中索引爲k 的元素(下標從 0 開始)
MountainArray.length() - 會返回該數組的長度

注意:

對 MountainArray.get 發起超過 100 次調用的提交將被視爲錯誤答案。此外,任何試圖規避判題系統的解決方案都將會導致比賽資格被取消。

爲了幫助大家更好地理解交互式問題,我們準備了一個樣例 “答案”,請注意這 不是一個正確答案。

示例 1:

輸入:array = [1,2,3,4,5,3,1], target = 3
輸出:2
解釋:3 在數組中出現了兩次,下標分別爲 2 和 5,我們返回最小的下標 2。


示例 2:

輸入:array = [0,1,2,4,2,1], target = 3
輸出:-1
解釋:3 在數組中沒有出現,返回 -1。

提示:

3 <= mountain_arr.length() <= 10000
0 <= target <= 10^9
0 <= mountain_arr.get(index) <= 10^9

  •  分析:

這道題有幾個重要的信息:

  1. 山脈數組是兩個有序數組的組成的數組,存在一個山峯值。
  2. 對 MountainArray.get 發起超過 100 次調用的提交將被視爲錯誤答案。
  3. 數據規模:3 <= mountain_arr.length() <= 10000

從這些信息中,我們得知:我們對其峯值進行查找需要使用一次二分查找,將峯值前半部分的查找需要使用一次二分查找,然後峯值後半部分的查找需要使用一次二分查找,一共三次二分查找

/**
 * // This is the MountainArray's API interface.
 * // You should not implement it, or speculate about its implementation
 * class MountainArray {
 *   public:
 *     int get(int index);
 *     int length();
 * };
 */

class Solution {
public:
    int binnary_search(MountainArray &mountainArr,int start,int end,int target,bool flag)
    {
        int l=start;
        int r=end;
        while(l<=r)
        {
            int mid=l+(r-l)/2;
            if(mountainArr.get(mid)==target)
                return mid;
            if(flag)
            {
                if(mountainArr.get(mid)>target)
                {
                    r=mid-1;
                }
                else
                    l=mid+1;   
            }
            else
            {
                if(mountainArr.get(mid)<target)
                {
                    r=mid-1;
                }
                else
                    l=mid+1;
            }

        }
        return -1;
    }
    int findInMountainArray(int target, MountainArray &mountainArr) {
        int length=mountainArr.length();
        if(length<3)
            return -1;
        int l=0;
        int r=length-1;
        while(l<r)
        {
            int mid=l+(r-l)/2;
            if(mountainArr.get(mid)>mountainArr.get(mid+1))
                r=mid;
            else
                l=mid+1;
        }
        int prior=binnary_search(mountainArr,0,l,target,true);
        if(prior!=-1)
            return prior;
        int back=binnary_search(mountainArr,l+1,length-1,target,false);
        return back;

    }
};

5.有序數組中的單一元素 (leetcode 540)

  • 題目描述:

給定一個只包含整數的有序數組,每個元素都會出現兩次,唯有一個數只會出現一次,找出這個數。

示例 1:

輸入: [1,1,2,3,3,4,4,8,8]
輸出: 2


示例 2:

輸入: [3,3,7,7,10,11,11]
輸出: 10

注意: 您的方案應該在 O(log n)時間複雜度和 O(1)空間複雜度中運行。

  • 分析:

從注意事項中,可以得知這道題算法時間複雜度需要爲 O(log n),那麼就會想到用二分查找.

  1. 首先我們需要知道,數組中的元素數量爲奇數,元素相對有序。
  2. 當mid=mid+1時,如果mid右邊的元素個數爲偶數,說明mid右邊含有偶數個數,那麼去掉mid+1後,就爲奇數個,說明單一元素在mid+1的右邊(l=mid+2)。
  3. 當mid=mid+1時,如果mid右邊的元素個數爲奇數,說明mid右邊含有奇數個數,那麼去掉mid+1後,就爲偶數個,說明單一元素在mid的左邊(r=mid-1)。
  4. 當mid=mid-1時,如果mid右邊的元素個數爲偶數,說明mid右邊含有偶數個數,那麼說明單一元素在mid-1的左邊(r=mid-2)。
  5. 當mid=mid-1時,如果mid右邊的元素個數爲奇數,說明mid右邊含有奇數個數,那麼說明單一元素在mid的右邊(l=mid+1)。
  6. 上述情況都不是,就直接返回該值。
class Solution {
public:
    int binary_search(vector<int>&nums)
    {
        int l=0;
        int r=nums.size()-1;
        while(l<r)
        {
            int mid=l+(r-l)/2;
            bool flag=(r-mid)%2==0;
            //中間元素和相鄰右邊的元素相等
            // cout<<"mid: "<<mid<<" l: "<<l<<" r: "<<r<<endl;
            if(nums[mid]==nums[mid+1])
            {
                //右半部分數量爲偶數
                if(flag)
                    l=mid+2;
                else
                    r=mid-1;
                    
            }
            else if(nums[mid]==nums[mid-1])
            {
                //右半部分數量爲偶數
                if(flag)
                    r=mid-2;
                else
                    l=mid+1;
            }
            else
                return nums[mid];
        }
        return nums[l];
    }
    int singleNonDuplicate(vector<int>& nums) {
        // if(nums.empty())
        //     return 0;
        // int a=0;
        // for(auto num:nums)
        //     a^=num;
        // return a;
        int a=binary_search(nums);
        return a;
    }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章