二分查找理论Binary Search代码实现及P1102 A-B 数对

Log函数与猜数

从一个1到100的序列中查找一个数,最坏要多少次才能找到?

方法1:1~n枚举

最简单的方法,效率低,最坏的情况枚举100次

方法2:每次从中间查询

  • 先猜区间 [1,100] 的中间数 50;
  • 判断是否猜中要找的数
  • 如果大,则数字范围在 [1,49][1,49];
  • 如果小,则在 [51,100][51,100];

每次反复继续猜测中间数

所以,最坏的猜测结果(不唯一):

100>50>25>12>6>3>1 

最多只需要 7 次。

这就叫做二分查找

log是个非常重要的函数,在这里我们用的是log base 2,而不是log base 10,那么表示出来是这样

\log (n) = x

那么,

2^{x} = n

大约 n 每乘上 2,logn 就加 1。

\log (100) ≈ 7 

结论:在 n 个元素中使用二分查找定位一个元素,最坏最坏情况下最多要找 log(n) 次。接下来正式进入二分查找。

二分查找

在一个1~n的递增序列中,怎么用二分查找数字x的下标?

在一个没用重复元素的递增序列中,用二分查找的方法最多需要查找log(n)次,也就是在一个区间内l~r不断的选取中间下标(mid),直到mid等于需要查找的数。这种查找方法在最坏的情况下能以很少的执行次数找到数。

而C++的二分查找实现是这样的:

  • 对于一个区间有左指针 l 和右指针 r
  • 在所有数字代表的区间,尝试中间下标 (mid)。
  • 如果中间下标对应的数字就是答案则输出答案。
  • 如果数字太小,则继续处理右区间。
  • 如果数字太大,则继续处理左区间。

程序中的重要变量:

  • a[100010] 输入的有序递增序列
  • n 序列中数字的个数
  • x 要查询的数字
  • l,r,mid 左指针,右指针,中间下表
  • ans 答案,中间下标等于x时下标的数
#include <bits/stdc++.h>
int a[1000010];
using namespace std;
int main() {
    int n, x;
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    cin >> x;
    int l = 1, r = n, ans = -1; 
    // 左指针是第一个元素的下标,右指针是最后一个元素的下标
    while (l<=r) {     // 只要左指针不大于右指针,就不断地循环。
        int mid = (l+r)/2; // 中间下标是左右指针的平均值
        if (a[mid] == x) {  // 如果中间下标对应的值刚好是要找的那个
            ans = mid;      // 记录答案并返回
            break;
        } else if (a[mid]>x) // 如果中间下标对应的值比要找到更大
            r = mid-1;  // 右指针缩到中间下标的左边一个
        else                // 如果中间下标对应的值比要找到小
            l = mid+1;  // 左指针缩到中间下标的右边一个
    }
    cout << ans; // 输出答案
}

查找第一个出现的位置时,可以这样执行

 while (l <= r) {
        int mid = (l + r) / 2;
        if (a[mid] == x) {  // 如果中间的数字等于要找的
            ans = mid;     // 记录答案位置
            r = mid-1;     // 局限在左区间
        } else if (a[mid] > x) // 如果中间数字大于要找的
            r = mid-1;     // 局限在左区间
        else                // 如果中间数字小于要找的
            l = mid+1;     // 局限在右区间
    }

查找最后一个出现的位置时,可以这样执行

while (l <= r) {
        int mid = (l + r) / 2;
        if (a[mid] == x) {  
            ans = mid;   
            l = mid+1;    
        } else if (a[mid] > x)  
            r = mid-1;     
        else                
            l = mid+1;     
    }

P1102 A-B 数对

题面

题目背景

出题是一件痛苦的事情!

相同的题目看多了也会有审美疲劳,于是我舍弃了大家所熟悉的 A+B Problem,改用 A-B 了哈哈!

题目描述

给出一串正整数数列以及一个正整数 C,要求计算出所有满足 A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。

输入格式

输入共两行。

第一行,两个正整数 N,C。

第二行,N 个正整数,作为要求处理的那串数。

输出格式

一行,表示该串正整数中包含的满足 A−B=C 的数对的个数。

输入输出样例

输入 #1

4 1
1 1 2 3

输出 #1

3

题解

重点:

二分查找之前需要排序成有序队列。

这道题需要满足A-B=C查找所有书对的个数,所以算则先固定某个A(因为此时C已经是确定的),找到了满足的数字B时需查找如果有同样的数,那么这个数第一次出现的下表和最后一次出现的下标。

步骤:

读入数据

对序列进行排序

对于每一个固定的a[i]查找b,以及b出现第一次的下表和最后一次的下表

计算第一次和最后一次下表的区间 y-x+1 并计入ans

返回ans值

代码

#include <bits/stdc++.h>
int a[200010], n, c;
long long ans = 0;
using namespace std;

int findx(int k) { // 找到第一次出现的位置
    int l = 1, r = n, ans = -1;
     while (l <= r) {
        int mid = (l + r) / 2;
        if (a[mid] == k) {  // 如果中间的数字等于要找的
            ans = mid;     // 记录答案位置
            r = mid-1;     // 局限在左区间
        } else if (a[mid] > k) // 如果中间数字大于要找的
            r = mid-1;     // 局限在左区间
        else                // 如果中间数字小于要找的
            l = mid+1;     // 局限在右区间
    }
    return ans;
}
int findy(int k) { // 找到最后一次出现的位置
    int l = 1, r = n, ans = -1;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (a[mid] == k) {  
            ans = mid;   
            l = mid+1;    
        } else if (a[mid] > k)  
            r = mid-1;     
        else                
            l = mid+1;     
    }
    return ans;
}
int main() {
    cin >> n >> c;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + n + 1);
    for(int i = 1; i <= n; i++) {
        int x = findx(a[i] + c);
        int y = findy(a[i]+c);
        if(x == -1) continue;
        ans += y-x+1; 
    }
    cout << ans; // 输出答案
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值