先简单谈谈二分
二分法
二分查找是一个时间效率极高的算法,尤其是面对大量的数据时,其查找效率是极高,时间复杂度是log(n)。
主要思想就是不断的对半折叠,每次查找都能除去一半的数据量,直到最后将所有不符合条件的结果都去除,只剩下一个符合条件的结果。
二分法的套路
首先 我们要确定一个区间[L,R]
然后 我们要找到一个性质,并且该性质满足一下两点:
①满足二段性
②答案是二段性的分界点。
整数二分的两类情况
[l,mid]和[mid + 1,r],[l,mid - 1]和[mid,r]
很多时候用二分解答题目的一大痛点就是对边界的判断模糊,那么让我们以题为例,更好的深入理解二分
1.请在序列1 4 7 9 10中寻找大于等于8的第一个数
解题思路
我们以中间值为分界点时,当中间值小于目标值时,左边的值(包括中间值都不满足条件,舍去,所以左边界缩小为mid+1,即第一类情况
注意:此时应让mid=(l+r+1)/2,因为假设mid = (L + R) / 2,那么L = (L + R) / 2 = (2 * R - 1) / 2 = L(很明显的奇数除以二,会进行向下取整),会造成死循环
代码实现
#include<iostream>
using namespace std;
int a[5]={1,4,7,9,10};
int right_bound(int x){
int l=0;int r=5;
while(l<r){
int mid=(l+r+1)>>1;
if(a[mid]>=x) r=mid;
else l=mid+1;
}
return a[r];
}
int main(){
cout<<right_bound(8);
}
2.请在序列1 4 7 9 10中寻找大于8的第一个数
解题思路
当中间值小于等于目标值时,中间值可以不在新的边界范围里,更改为l=mid,即第二种情况
代码实现
#include<iostream>
using namespace std;
int a[5]={1,4,7,9,10};
int left_bound(int x){
int l=0;int r=5;
while(l<r){
int mid=(l+r+1)>>1;
if(a[mid]<=x) l=mid;
else r=mid-1;
}
return a[r];
}
int main(){
cout<<left_bound(8);
}
二分的深入练习
力扣33搜索旋转排序数组
整数数组 nums 按升序排列,数组中的值 互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -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
示例 3:
输入:nums = [1], target = 0
输出:-1
提示:
1 <= nums.length <= 5000
-104 <= nums[i] <= 104
nums 中的每个值都 独一无二
题目数据保证 nums 在预先未知的某个下标上进行了旋转
-104 <= target <= 104
题目分析
给定一个升序的数组和目标值,在数组经过向右旋转多次后,搜索目标值
解题思路
利用二分法,因为原来的数组是升序的,即使经过旋转后,数组仍然呈现两部分的严格递增,且前一部分的初始值一定大于另一部分的末尾值,这时我们可以分为两种情况:
1.首先将旋转后的数组第一个值与中间值比较,如果中间值大,那么旋转后的分割点(也就是原数组的nums[0])在中间值以后,那么我们可以保证中间值前面都是严格递增
然后再利用二分,当目标值小于中间值时,改变右边界,注意:改变的同时一定要保证目标值也小于旋转后的nums[0]因为它有可能在后半部分
2.当中间值小于数组第一个值时,分割点在中间值前面,同理可以保证中间值后面都是严格递增的
代码实现
class Solution {
public:
int search(vector<int>& nums, int target) {
int n=nums.size();
if(!n)return -1;
if(n==1)return nums[0]==target?0:-1;
int l=0,r=n-1;
while(l<=r){
int mid=(l+r)/2;
if(nums[mid]==target)return mid;
if(nums[0]<=nums[mid]){
if(target<nums[mid]&&target>=nums[0]){
r=mid-1;
}else{
l=mid+1;
}
}else{
if(target>nums[mid]&&target<=nums[n-1]){
l=mid+1;
}else{
r=mid-1;
}
}
}
return -1;
}
};
力扣287寻找重复数
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
示例 1:
输入:nums = [1,3,4,2,2]
输出:2
示例 2:
输入:nums = [3,1,3,4,2]
输出:3
示例 3 :
输入:nums = [3,3,3,3,3]
输出:3
题目分析
一共有n+1个整数,但只有n个数字,相当于10个苹果放进9个抽屉里,一定有一个抽屉重复有两个苹果,找出那个重复的数字
解题思路
利用二分的思想,并将时间转为空间,取数组的中间值,正常来说数组中小于中间值的个数应该等于n/2,如果数组中小于中间值的个数大于一半,那么重复的数字一定小于中间值,改变右边界
注意:本题中改变的右边界不是无序数组中的边界,而是改变n个数的有序数组中的边界值
代码实现
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int ans=-1;
for(int i=0,j=nums.size()-1;i<=j;){
int mid=(i+j)/2;
int cnt=0;
for(int k=0;k<nums.size();k++){
cnt+=nums[k]<=mid;
}
if(cnt<=mid){
i=mid+1;
}else{
j=mid-1;
ans=mid;
}
}
return ans;
}
};
这篇博客里还有有关二分的练习
二分练习