目錄
-
二分法的原理
二分查找,又叫做折半查找,是一種在有序數組中查找某一特定元素的搜索算法。
1.二分查找(模板1)
這是我們經常見到的二分查找算法,主要作用是在循環體中查找某一元素,主要存在三個分支,其中兩個分支用於邊界的搜索。
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]之間,而且這個區間是有序的。那麼我們就可以使用二分查找法。
- 如果中間元素 mid 等於x的平方根,那麼直接返回。
- 如果x/mid的值大於mid,說明x的平方根在mid的右邊,則l=mid+1;
- 如果x/mid的值小於mid,說明x的平方根在mid的左邊,則r=mid-1;
- 如果沒找到合適的值,則返回向下取整的值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
- 分析:
這道題有幾個重要的信息:
- 山脈數組是兩個有序數組的組成的數組,存在一個山峯值。
- 對 MountainArray.get 發起超過 100 次調用的提交將被視爲錯誤答案。
- 數據規模: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),那麼就會想到用二分查找.
- 首先我們需要知道,數組中的元素數量爲奇數,元素相對有序。
- 當mid=mid+1時,如果mid右邊的元素個數爲偶數,說明mid右邊含有偶數個數,那麼去掉mid+1後,就爲奇數個,說明單一元素在mid+1的右邊(l=mid+2)。
- 當mid=mid+1時,如果mid右邊的元素個數爲奇數,說明mid右邊含有奇數個數,那麼去掉mid+1後,就爲偶數個,說明單一元素在mid的左邊(r=mid-1)。
- 當mid=mid-1時,如果mid右邊的元素個數爲偶數,說明mid右邊含有偶數個數,那麼說明單一元素在mid-1的左邊(r=mid-2)。
- 當mid=mid-1時,如果mid右邊的元素個數爲奇數,說明mid右邊含有奇數個數,那麼說明單一元素在mid的右邊(l=mid+1)。
- 上述情況都不是,就直接返回該值。
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;
}
};