算法——二分

二分算法是我觉得在基础算法篇章中最难的算法。二分算法的原理以及模板其实是很简单的,主要的难点在于问题中的各种各样的细节问题。因此,大多数情况下,只是背会二分模板并不能解决题目,还要去处理各种乱七八糟的边界问题。

一、二分查找

【算法原理】

当我们的解具有二段性时,就可以使用二分算法找出答案:

根据待查找区间的中点位置,分析答案会出现在哪一侧;

接下来舍弃一半的待查找区间,转而在有答案的区间内继续使用二分算法查找结果。

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

复制代码
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

复制代码
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

复制代码
输入:nums = [], target = 0
输出:[-1,-1]

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums 是一个非递减数组
  • -109 <= target <= 109

【解法一】暴力解法->从前往后扫描数组

慢就慢在没有利用数组有序的特性,O(N)

【解法二】二分算法

契机:发现解集中,存在二段性

1.查找起始位置

细节问题:

1)while循环里面的判断如何写?

while ( left < right ) √

while ( left < right ) × 死循环

一直命中第一个条件,left=right=0后,一直死循环

2)求中点的方式?

( left + right ) / 2 ; √

( left + right + 1) / 2 ; ×

3)二分结束之后,相遇点的情况

2.查找终止位置

1)while循环里面的判断如何写?

while ( left < right ) √

while ( left < right ) × 死循环

2)求中点的方式?

( left + right ) / 2 ; × 死循环

( left + right + 1) / 2 ; √

【模板】

cpp 复制代码
//查找区间左端点
int l = 1, r = n;
while (l < r)
{
	int mid = (l + r) / 2;
	if (check(mid))
		r = mid;
	else
		l = mid + 1;
}
//二分结束之后可能需要判断是否存在结果
cpp 复制代码
//查找区间右端点
int l = 1;r = n;
while (l < r)
{
	int mid = (l + r + 1) / 2;
	if (check(mid))
		l = mid;
	else
		r = mid - 1;
}
//二分结束之后可能需要判断是否存在结果

【注】为防止溢出,求中点时:

mid = l + ( r - l ) / 2 ;

【时间复杂度】

每次二分都会砍掉一半的查找区域,O(logN)

cpp 复制代码
class Solution 
{
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        int n=nums.size();
        if(n==0)
            return {-1,-1};
        if(nums[0]>target)
            return {-1,-1};
        if(nums[n-1]<target)
            return {-1,-1};
        int l=0;
        int r=n-1;
        int m=(l+r)/2;
        //查找起始位置
        while(l<r)
        {
            m=(l+r)/2;
            if(nums[m]>=target)
                r=m;
            else
                l=m+1;
        }
        //l或r所指的位置有可能是最终答案
        if(nums[l]!=target)
            return {-1,-1};
        int retleft=l;
        l=0,r=n-1;
        //查找终止位置
        while(l<r)
        {
            m=(l+r+1)/2;
            if(nums[m]<=target)
                l=m;
            else
                r=m-1;
        }
        return {retleft,l};
    }
};

【二分问题解决流程】

1.先画图分析,确定使用左端点模板还是右端点模板,还是两者配合一起使用;

2,二分出结果之后,不要忘记判断结果是否存在,二分问题细节众多,一定要分析全面。

【STL中的二分查找】

<algorithm>

1.lower_bound:大于等于x的最小元素,返回的是迭代器;时间复杂度:O(logN)。

2.upper_bound:大于x的最小元素,返回的是迭代器。时间复杂度:O(logN)。

二者均采用二分实现。但是STL中的二分查找只能适用于"在有序的数组中查找",如果是二分答案就不能使用。因此还是需要记忆二分模板。

二、二分答案

P2440 木材加工 - 洛谷

题目描述

木材厂有 n 根原木,现在想把这些木头切割成 k 段长度为 l 的小段木头(木头有可能有剩余)。

当然,我们希望得到的小段木头越长越好,请求出 l 的最大值。

木头长度的单位是 cm,原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。

例如有两根原木长度分别为 11 和 21,要求切割成等长的 6 段,很明显能切割出来的小段木头长度最长为 5。

输入格式

第一行是两个正整数 n,k,分别表示原木的数量,需要得到的小段的数量。

接下来 n 行,每行一个正整数 Li​,表示一根原木的长度。

输出格式

仅一行,即 l 的最大值。

如果连 1cm 长的小段都切不出来,输出 0

输入输出样例

输入 #1复制

复制代码
3 7
232
124
456

输出 #1复制

复制代码
114

说明/提示

数据规模与约定

对于 100% 的数据,有 1≤n≤105,1≤k≤108,1≤Li​≤108(i∈[1,n])。

准确来说,应该叫做「二分答案+判断」。

二分答案可以处理大部分「最大值最小」以及「最小值最大」的问题。如果「解空间」在从小到大的「变化」过程中,「判断」答案的结果出现「二段性」,此时我们就可以「二分」这个「解空间」,通过「判断」,找出最优解。

刚接触的时候,可能觉得这个「算法原理」很抽象。没关系,练习过后,你会发现这个「二分答案」的原理其实很容易理解,重点是如何去「判断」答案的可行性。

x表示:切割出来的小段的长度

c表示:在x的基础下,最多能切出来多少段

k表示:最终要切割的段数

解法一:暴力解法

枚举所有的切割长度x

求出在x的情况下,能够切出来多少段

解法二:利用二分来优化

x增大,c减小。x减小,c增大。

二段性

Calc(x)

计算切割长度为x的时候,能切出来多少段

相关推荐
七点半77021 小时前
c++基本内容
开发语言·c++·算法
嵌入式进阶行者21 小时前
【算法】基于滑动窗口的区间问题求解算法与实例:华为OD机考双机位A卷 - 最长的顺子
开发语言·c++·算法
嵌入式进阶行者21 小时前
【算法】用三种解法解决字符串替换问题的实例:华为OD机考双机位A卷 - 密码解密
c++·算法·华为od
罗湖老棍子21 小时前
信使(msner)(信息学奥赛一本通- P1376)四种做法
算法·图论·dijkstra·spfa·floyd·最短路算法
生成论实验室1 天前
生成论之基:“阴阳”作为元规则的重构与证成——基于《易经》与《道德经》的古典重诠与现代显象
人工智能·科技·神经网络·算法·架构
啊董dong1 天前
noi-2026年1月07号作业
数据结构·c++·算法·noi
l1t1 天前
DeepSeek辅助编写的利用唯一可选数求解数独SQL
数据库·sql·算法·postgresql
星火开发设计1 天前
二叉树详解及C++实现
java·数据结构·c++·学习·二叉树·知识·期末考试
WJSKad12351 天前
传送带物体检测识别_基于YOLO11与RGCSPELAN改进算法_工业视觉检测系统
人工智能·算法·视觉检测