分享会之谈谈二分

先简单谈谈二分

二分法

二分查找是一个时间效率极高的算法,尤其是面对大量的数据时,其查找效率是极高,时间复杂度是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;
    }
};

这篇博客里还有有关二分的练习
二分练习

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值