典中典的题目,为让自己加深印象,遂决定写一篇解法总结
原题链接
文章目录
题目信息
题目描述
给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C A−B=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
输入格式
输入共两行。
第一行,两个正整数 N , C N,C N,C。
第二行, N N N 个正整数,作为要求处理的那串数。
输出格式
一行,表示该串正整数中包含的满足 A − B = C A - B = C A−B=C 的数对的个数。
样例
输入
4 1
1 1 2 3
输出
3
说明/提示
对于 75 % 75\% 75% 的数据, 1 ≤ N ≤ 2000 1 \leq N \leq 2000 1≤N≤2000。
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2 × 1 0 5 1 \leq N \leq 2 \times 10^5 1≤N≤2×105, 0 ≤ a i < 2 30 0 \leq a_i <2^{30} 0≤ai<230, 1 ≤ C < 2 30 1 \leq C < 2^{30} 1≤C<230。
解题思路及代码
🌟法一:排序+二分查找
🎯核心思路
- 步骤拆解
- 将数组排序,便于后续查找
- 对于每个元素 B,计算目标值 A = B + C
- 使用二分查找快速统计数组中等于 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)
🌟法二:双指针法
🎯核心思路
-
排序预处理:使数列具有单调性(有序性),这样能够以 O ( n ) O(n) O(n)的时间复杂度查找到指定的数字(双指针单向移动无需回溯)。
-
双指针协作:
-
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)