leetcode 經典二分查找算法題目(思路、方法、code)
文章目錄
- leetcode 經典二分查找算法題目(思路、方法、code)
- [35. 搜索插入位置](https://leetcode-cn.com/problems/search-insert-position/)
- [69. x 的平方根](https://leetcode-cn.com/problems/sqrtx/)
- [34. 在排序數組中查找元素的第一個和最後一個位置](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/)
- [154. 尋找旋轉排序數組中的最小值 II](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/)
- [33. 搜索旋轉排序數組](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/)
35. 搜索插入位置
給定一個排序數組和一個目標值,在數組中找到目標值,並返回其索引。如果目標值不存在於數組中,返回它將會被按順序插入的位置。
你可以假設數組中無重複元素。
示例 1:
輸入: [1,3,5,6], 5
輸出: 2
示例 2:
輸入: [1,3,5,6], 2
輸出: 1
分析:由於數組是一個有序數組,故可以採用二分搜索找位置,可以在 的時間複雜度內找到。
- 若target在數組中出現,則典型的二分查找方法即可
- 若target在數組中沒有出現
- 若target<nums[mid] && target>nums[mid-1] ,說明返回的下標應該是 mid
- 若target>nums[mid] && target<nums[mid+1], 說明返回的下標應該是mid+1
- 若mid==0 || mid==size-1 ,也就是說target應該的位置在於數組外
- 若target<nums[0],返回0
- 若target>nums[size-1],返回size
//暴力的方法:
class Solution {
public:
int searchInsert(vector<int>& nums, int target)
{
for(int i=0;i<nums.size();i++)
{
if(nums[i]>=target)
return i;
}
return nums.size();
}
};
//二分查找的方法:
class Solution {
public:
int searchInsert(vector<int>& nums, int target)
{
int index=-1;
int begin=0,end=nums.size()-1;
while(index==-1) //未找到位置
{
int mid=(begin+end)/2;
if(target==nums[mid])
index=mid;
else if(target<nums[mid]) //mid處值大於target
{
if(mid==0||target>nums[mid-1]) //短路,不用判斷mid-1非法
index=mid; //確定了target的位置
end=mid-1; //因爲已經判斷過mid的位置,直接mid-1即可
}
else if(target>nums[mid])
{
if(mid==nums.size()-1||target<nums[mid+1])
index=mid+1;
begin=mid+1;
}
}
return index;
}
};
69. x 的平方根
實現 int sqrt(int x) 函數。
計算並返回 x 的平方根,其中 x 是非負整數。
由於返回類型是整數,結果只保留整數的部分,小數部分將被捨去。
示例 1:
輸入: 4
輸出: 2
示例 2:
輸入: 8
輸出: 2
說明: 8 的平方根是 2.82842...,
由於返回類型是整數,小數部分將被捨去。
分析:x的平方根,在此處是指最大的滿足 r*r<=x的r值,故可以用二分查找,因爲該值一定在0~x間,故可以逐步判斷每次減少一半區間,在 的時間複雜度內找到其應該在的位置
class Solution {
public:
int mySqrt(int x)
{
int left = 0, right = x, ans = -1;
while (left <= right)
{
int mid = (left+right)/ 2;
if ((long long)mid * mid <= x) //避免溢出
{
ans = mid;
left = mid + 1;
}
else
{
right = mid - 1;
}
}
return ans;
}
};
34. 在排序數組中查找元素的第一個和最後一個位置
給定一個按照升序排列的整數數組 nums,和一個目標值 target。找出給定目標值在數組中的開始位置和結束位置。
你的算法時間複雜度必須是 O(log n) 級別。
如果數組中不存在目標值,返回 [-1, -1]。
示例 1:
輸入: nums = [5,7,7,8,8,10], target = 8
輸出: [3,4]
示例 2:
輸入: nums = [5,7,7,8,8,10], target = 6
輸出: [-1,-1]
分析:實際上,要找的位置,可以這樣理解:如果target沒有出現,則返回[-1,-1],否則,返回target的最左索引和target的最右索引。由於數組是排序的,因此採用二分查找,思路如下:
- 首先利用二分查找最左索引,最左索引的特徵是該座標左側應當不是該值,該座標處是該值
- 然後同樣方法查找最右索引,最右索引的特徵是該座標右側不是該值,該座標處是該值
- 返回即可
class Solution {
public:
int left_bound(vector<int> & nums,int target)
{
int begin=0,end=nums.size()-1;
while(begin<=end)
{
int mid=(begin+end)/2;
if(target==nums[mid]) //相等時候還要找到最左側的該值
{
if(mid==0||nums[mid-1]<target) //說明是最左的索引了
return mid;
end=mid-1; //否則令end左移
}
else if(target<nums[mid])
end=mid-1;
else if(target>nums[mid])
begin=mid+1;
}
return -1;
}
int right_bound(vector<int> & nums,int target)
{
int begin=0,end=nums.size()-1;
while(begin<=end)
{
int mid=(begin+end)/2;
if(target==nums[mid]) //相等時候還要找到最右側的該值
{
if(mid==nums.size()-1||nums[mid+1]>target) //說明是最右的索引了
return mid;
begin=mid+1; //否則令end左移
}
else if(target<nums[mid])
end=mid-1;
else if(target>nums[mid])
begin=mid+1;
}
return -1;
}
vector<int> searchRange(vector<int>& nums, int target)
{
vector<int> result;
int left=left_bound(nums,target);
int right=0;
if(left==-1)
right=-1;
else
right=right_bound(nums,target);
result.push_back(left);
result.push_back(right);
return result;
}
};
154. 尋找旋轉排序數組中的最小值 II
假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
( 例如,數組 [0,1,2,4,5,6,7] 可能變爲 [4,5,6,7,0,1,2] )。
請找出其中最小的元素。
注意數組中可能存在重複的元素。
示例 1:
輸入: [1,3,5]
輸出: 1
示例 2:
輸入: [2,2,2,0,1]
輸出: 0
分析:原始數組爲一個非遞減排序,因此,我們旋轉後,可知該數組應當是兩個非遞減序列組成,並且右側序列的最右端(即最大值)一定不大於左側序列的最左端(即最小值)
可以舉一個具體例子來分析
給定數組 1,2,3,4,5
旋轉後可以爲 3,4,5,1,2
因此可以看出 3,4,5 1,2 分別是非遞減序列
因此根據該性質,易知可用二分查找
- 找到區域邊界兩個位置 ,
- 如果 左<右 ,很顯然數組沒有發生旋轉,否則一定是左<=右
- 找到中間位置
- 如果中間位置的值小於右側,說明最小值在右側,令 ,返回最初循環
- 如果中間位置的值大於右側,說明最小值在左側,令 ,返回最初循環
- 如果中間位置的值等於右側,則沒辦法確定最小值的位置,此時令 ++ 繼續推進循環即可
- 直至i=j,即找到最小值
class Solution {
public:
int findMin(vector<int>& nums)
{
int size=nums.size();
int i=0,j=size-1;
while(i<j)
{
if(nums[i]<nums[j]) //說明數組沒有旋轉
return nums[i];
int mid=(i+j)>>1;
if(nums[mid]>nums[j])
{
i=mid+1;continue; //注意這裏i=mid+1
}
if(nums[mid]<nums[j])
{
j=mid; //j=mid 主要是讓i和j必須保持相近,避免二者相鄰時出現死循環
continue;
}
if(nums[mid]==nums[j])//兩個值相等的話沒辦法二分,只能逐步右移
{
i++;
}
}
return nums[i];
}
};
33. 搜索旋轉排序數組
假設按照升序排序的數組在預先未知的某個點上進行了旋轉。
( 例如,數組 [0,1,2,4,5,6,7] 可能變爲 [4,5,6,7,0,1,2] )。
搜索一個給定的目標值,如果數組中存在這個目標值,則返回它的索引,否則返回 -1 。
你可以假設數組中不存在重複的元素。
你的算法時間複雜度必須是 O(log n) 級別。
示例 1:
輸入: nums = [4,5,6,7,0,1,2], target = 0
輸出: 4
示例 2:
輸入: nums = [4,5,6,7,0,1,2], target = 3
輸出: -1
分析:爲了達到 的時間複雜度,因此需要採用二分查找。該題採用二分查找時需要注意的是,每次找到中點後,不應該直接去比較中點與target的值,而應該首先確定左右兩個區間是否是完全遞增的。完全遞增的區間和兩個分別遞增的區間的判斷方式不一樣。
- 每次找到中點位置
- 如果target等於中點處的值,返回即可
- 如果target小於中點處的值
- 如果左側是遞增區間
- target>=nums[begin] ,則在[begin,mid-1]中查找
- 否則在[mid+1,end]中查找
- 如果左側是旋轉區間
- 因爲target小於中點處的值,故一定在旋轉區間,在[begin,mid-1]中查找
- 如果左側是遞增區間
- 如果target大於中點處的值
- 如果左側是遞增區間,
- 則直接在[mid+1,end]中查找
- 如果左側是旋轉區間
- 如果target>=nums[begin],則在[begin,mid-1]中查找
- 否則在[mid+1,end]中查找
- 如果左側是遞增區間,
class Solution {
public:
int search(vector<int>& nums, int target)
{
int begin=0,end=nums.size()-1;
while(begin<=end)
{
int mid=(begin+end)/2;
if(target==nums[mid])
return mid;
else if(target<nums[mid])
{
if(nums[begin]<nums[mid])//左側遞增
{
if(target>=nums[begin])
end=mid-1;
else
begin=mid+1;
}
else if(nums[begin]>nums[mid])//左側旋轉
{
end=mid-1;
}
else if(nums[begin]==nums[mid]) //只剩下兩個元素
{
begin=mid+1;
}
}
else if(target>nums[mid])
{
if(nums[begin]<nums[mid])//左側遞增
{
begin=mid+1;
}
else if(nums[begin]>nums[mid]) //左側旋轉
{
if(target>=nums[begin])
end=mid-1;
else
begin=mid+1;
}
else if(nums[begin]==nums[mid]) //只剩兩個元素
begin=mid+1;
}
}
return -1;
}
};