首页 > 其他分享 >茴香豆的茴有四种写法,那二分有几种写法?

茴香豆的茴有四种写法,那二分有几种写法?

时间:2024-10-19 23:21:36浏览次数:8  
标签:二分 可行 int 茴香豆 查找 取整 写法

《编程珠玑》一书的作者 Jon Bentley 曾经说过:“90%的程序员无法正确实现二分查找算法...”,今天,本文将带领你会写二分。

经典写法

现在我们来求解这样一个通用的二分查找问题:有一个不下降序列 $ a $,我们要从其中所有找到大于等于 $ k $ 的数的最小的下标。

bool check(int index) {return a[index] >= k;}

while (l < r) {
	int m = (l + r) / 2; // 或 l + (r - l) / 2 防止溢出
	check(m) ? (r = m) : (l = m + 1);
} return l;

序列一定可以划分为两部分,左半部分全部小于 k, 右半部分全部大于等于 k,我们找到右半部分的第一个元素。

l 和 r 通常赋初值为 0 和 数组末下标。接下来,已知答案在 \([l, r]\) 内,首先选取一个靠中的位置 $ m $,若此处已经满足大于等于条件,则他位于右半边,答案应位于 m 左侧或 m 本身,所以我们修改新的可能区间为 \([l, m]\);反过来,若此处不满足大于等于条件,则他位于左半边,答案应位于 m 右侧(不可能包含其本身),所以我们修改新的可能区间为 \([m+1, r]\)。

当 l 距离 r 仅 1 时,int m = (l + r) / 2将向零取整得到 l, 若其可行则 r 会被赋值 l,否则 l 会被赋值 r,无论情况,最终都会使得 l 和 r 相等,循环结束。此时 l (或 r)就是正确答案。

处理例外情况

需要特别说明的是,当 \(k\) 比序列中最大的数还大时,不存在符合条件的数,得到的结果无效,二分查找的结果需要进行验证。通常来说,如果下标 0 开,初始 l = 0, r = len,查找到的 \(l\) 就会超出序列的范围。因此,在使用二分查找后,可以检查 \(l\) 是否在有效范围内并且 \(a[l] \ge k\),以确保找到的答案有效,或者在二分查找前先将 $ k $ 和末数对比。

另一种经典写法

接下来,我们讨论另一种经典写法。假设问题变为:我们仍然有一个不下降序列 \(a\),但是这一次我们需要找到其中小于等于 \(k\) 的最大下标。

bool check(int index) {return a[index] <= k;}

while (l < r) {
	int m = (l + r + 1) / 2; // l + (r - l + 1) / 2
	check(m) ? (r = m - 1) : (l = m);
} return l;

和上文类似,在每次循环中,如果位置 \(m\) 满足条件 \(a[m] \le k\),说明 \(m\) 是可行的,因此我们将 \(l\) 赋值为 \(m\),即答案可能出现在当前 \(m\) 位置或更右侧;如果 \(m\) 不满足条件,则说明 \(m\) 太大,因此将 \(r\) 赋值为 \(m - 1\),在下一次循环中继续查找左侧部分。

但是,在更新中间位置 \(m\) 时,使用 int m = (l + r + 1) / 2,即向上取整。当 l 距离 r 仅 1 时,m 将被赋为 r, 若其可行则 l 会被赋值 r,否则 r 会被赋值 l,循环可以正常结束。

两个问题的根本区别在于可行域所在的位置不一样,一个是可行域在左侧,求其右端;另一个则是可行域在右侧,求其左端。这导致了检查 m 后对 l 和 r 修改的不同,又进一步导致 m 不在正中央时的取整条件不同,确保循环在最后一步正确结束而不是无限循环。因此,两套方案一一对应,不可以混用。

下标取整问题

我们现在知道“若 l = m + 1,则取 m 应向下;若 r = m - 1,则取 m 时应向上”。不过 (l + r) / 2 在 C++ 中是向零取整的。这就意味着在 l 和 r 可取负数时,结果将不正确,m 会向上取造成死循环。解决方案之一是改用 l + (r - l) / 2,而对于写法二,或者其他取整规则不同的语言,也可自行推导。关键不是死记硬背形式,而是记住如何取整。

带记录答案的写法

int ans;
while (l <= r) {
	int m = (l + r) / 2;
	check(m) ? (ans = m, r = m - 1) : (l = m + 1);
} return ans;

无论 m 是否可行,新的区间里总是不取它。这样的话 m 向任何一方取整都可以。同时,无论如何取整都会出现,r - l 跳过 1 直接达到 0 的情况,因此 l = r 时还需要再循环一次,最终结束时 l > r,ans 是答案。

这种写法无视可行域的方向,但是需要一个额外的变量。

标签:二分,可行,int,茴香豆,查找,取整,写法
From: https://www.cnblogs.com/ofnoname/p/18173495

相关文章

  • 704.二分查找
    题目给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果目标值存在返回下标,否则返回-1。示例1:输入:nums=[-1,0,3,5,9,12],target=9输出:4解释:9出现在nums中并且下标为4示例2:输入:nums=[-1,0,3,......
  • 【数据结构与算法】之二分查找
    二分查找(BinarySearch)是一种在有序数组中查找特定元素的搜索算法。它通过比较数组中间元素与目标值来工作,从而将搜索范围缩小到一半,也称折半查找,是一种非常高效的工作于有序数组的查找算法。本文主要介绍二分查找及其几个变种,包括基础版、改变版、平衡版和Java版,以及Leftmost......
  • UOJ 80:二分图最大权匹配 ← KM算法
    【KM算法简介】Kuhn-Munkres算法,简称KM算法,是用于“二分图带权最大匹配问题”的经典算法。【题目来源】https://uoj.ac/problem/80【题目描述】从前一个和谐的班级,有nl个是男生,有nr个是女生。编号分别为1,……,nl和1,……,nr。有若干个这样的条件:第v个男生......
  • lambda表达式的写法2
    一、类名::静态方法名publicclassStaff{privateStringid;privateStringname;privateintage;privateintsalary;publicStaff(){}publicStaff(Stringid,Stringname,intage,intsalary){this.id=id;t......
  • lambda表达式的写法1
    一、lambda表达式的含义Lambda表达式是Java8引入的一种简洁的语法,用于表示匿名函数或传递行为。它使得我们可以更简洁地表达代码中的行为和函数逻辑,特别是在使用函数式接口时(如Consumer、Supplier、Function<T,R>等)。Lambda表达式可以大大简化代码,尤其是当我们需要为接口......
  • C. New Game (二分)
    时隔多年又做题了这不得来水一篇博客题意:给出n个数,取一段连续的数字,最大数和最小数的差不超过k,使得取的数最多。解:对于每一个数,找到第最后一个连续的且与其差值不大于k的数,数一数期间一共有几个,然后取最大值。实现上先处理出连续的段,对于每一个数,找到对应的段,二分找出差值不大于......
  • 【算法】C++中的二分查找
    ......
  • 信息学奥赛复赛复习18-CSP-J2022-01解密-二分答案、二分找边界、二分时间复杂度、二分
    PDF文档公众号回复关键字:202410171P8814[CSP-J2022]解密[题目描述]给定一个正整数k,有k次询问,每次给定三个正整数ni,ei,di,求两个正整数pi,qi,使ni=pi×qi、ei×di=(pi−1)(qi−1)+1[输入格式]第一行一个正整数k,表示有k次询问。接下来k行,第i行三个正整数......
  • 翻转链表常用写法
    翻转链表常用写法循环写法classSolution{public:ListNode*reverseList(ListNode*head){ListNode*prev=nullptr,*next=nullptr,*now=head;while(now){next=now->next;now->next=prev;prev=......
  • 算法->二分查找
    二分查找找>=target的第一个位置找<=target的最后一个位置classSolution{public://找>=target的第一个位置intbinarySearchLeft(vector<int>&nums,inttarget){intleft=0,right=nums.size()-1;while(left<=right){......