【刷题笔记】洛谷P1102 A-B数对

典中典的题目,为让自己加深印象,遂决定写一篇解法总结
原题链接


题目信息

题目描述

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

输入格式

输入共两行。

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

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

输出格式

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

样例

输入

4 1
1 1 2 3

输出

3

说明/提示

对于 75 % 75\% 75% 的数据, 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1N2000

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1N2×105 0 ≤ a i < 2 30 0 \leq a_i <2^{30} 0ai<230 1 ≤ C < 2 30 1 \leq C < 2^{30} 1C<230


解题思路及代码

🌟法一:排序+二分查找

🎯核心思路
  • 步骤拆解
  1. 将数组排序,便于后续查找
  2. 对于每个元素 B,计算目标值 A = B + C
  3. 使用二分查找快速统计数组中等于 A 的元素数量
  • 关键步

    ans += upper_bound(x, x+n, x[i]+c) - lower_bound(x, x+n, x[i]+c);
    
  • lower_bound 找第一个 ≥A 的位置

  • upper_bound 找第一个 >A 的位置

  • 二者差值即为等于 A 的元素数量

✨代码
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
ll x[1000000],c,ans=0;
int main() {
    int n;
    cin>>n>>c;
    for(int i=0;i<n;i++) {
        cin>>x[i];
    }
    sort(x,x+n);
    for(int i=0;i<n;i++) {
        ans+=(upper_bound(x,x+n,x[i]+c)-lower_bound(x,x+n,x[i]+c));//关键步骤
    }
    cout<<ans;
    return 0;
}
💡复杂度分析
  • 时间复杂度:O(n log n)(排序 + n次二分查找)
  • 空间复杂度:O(n)

🌟法二:双指针法

🎯核心思路
  1. 排序预处理:使数列具有单调性(有序性),这样能够以 O ( n ) O(n) O(n)的时间复杂度查找到指定的数字(双指针单向移动无需回溯)。

  2. 双指针协作

    • l指针:寻找第一个不小于a[i]-C的位置

    • r指针:寻找第一个大于a[i]-C的位置

✨代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
ll x[1000000],c,ans=0;
int main() {
    int n;
    cin>>n>>c;
    for(int i=0;i<n;i++) {
        cin>>x[i];
    }
    sort(x,x+n);
    int l=0,r=0;
    for(int i=0;i<n;i++) {
        //虽然本题中一定满足l<=r<i<n,但最好养成先检查边界再访问元素的习惯
        while(l<n&&x[l]<x[i]-c) l++;
        while(r<n&&x[r]<=x[i]-c) r++;
        ans+=r-l;//收割区间长度
    }
    cout<<ans;
    return 0;
}
💣注意点
  • 收割区间长度时,无需先判断x[i]-x[l]是否等于c再更新ans,这是为什么嘞?

(此乃思考的分割线)

  • 假装你已经思考完了,正确原因如下:
    ①当不存在解(即x[i]-x[l]!=c)时:左边界(l)和右边界(r)会重合→r-l=0,ans+=0
    ②而存在多个解时,r-l就会自动统计所有解的数量啦
💡复杂度分析
  • 时间复杂度:O(n log n)(排序) + O(n)(扫描)
  • 空间复杂度:O(1)(原地排序)

🌟法三:hash表大法

🎯核心思路:

把a储存在普通数组,b用map值表示对于每一个A,查询有多少B满足B=A-C。

✨代码
#include<iostream>
#include<cstdio>
#include<map>
using namespace std;
map<int,int> nums;
int a[200010];
long long ans=0;
int main() {
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        nums[a[i]]++;//构建数字->次数的映射
    }
    for(int i=1;i<=n;i++) {
        ans+=nums[a[i]-c];//获取匹配数
    }
    cout<<ans;
    return 0;
}
💡复杂度分析
  • 时间复杂度:O(n)
  • 空间复杂度:O(n) → 需要哈希表存储空间
  • 拓展:使用unordered_map代替map可获得更优常数时间

选择指南

  • 🚀追求速度:哈希表大法(注意数字范围)
  • 📚内存敏感:双指针优雅解法
  • 🎯通用之选:二分查找法稳如老狗
    在这里插入图片描述

“好的算法能让计算机为你写诗,坏的算法会让电脑当场去世” —— 鲁迅(bushi)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值