1. 问题背景与核心需求
这道题目来自AcWing在线题库的第800题,属于双指针算法中的经典问题。题目给出两个升序排列的数组A和B,以及一个目标值x,要求找出满足A[i] + B[j] = x的所有元素对(i,j),且要求时间复杂度优于O(n²)。
在实际工程中,类似场景非常常见。比如在电商系统中,我们需要从用户浏览记录和商品库中匹配符合特定价格组合的商品;在游戏开发中,可能需要从两个属性池中挑选满足战力要求的角色组合。这类问题的核心在于如何高效地利用数据的有序性来降低搜索复杂度。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法思路解析
2.1 暴力解法及其局限性
最直观的解法是双重循环遍历:
python复制for i in range(len(A)):
for j in range(len(B)):
if A[i] + B[j] == x:
return (i, j)
这种方法时间复杂度为O(n²),当数组长度较大时(比如10⁵量级),计算量会达到10¹⁰次操作,在现代计算机上需要数秒才能完成,无法满足算法竞赛和实际工程中的性能要求。
2.2 双指针算法的优化原理
观察到两个数组都是升序排列的,我们可以利用这个性质进行优化。设置指针i初始指向A的开头,指针j初始指向B的末尾:
- 当A[i] + B[j] > x时,说明当前和太大,应该尝试减小B的元素,故j左移
- 当A[i] + B[j] < x时,说明当前和太小,应该尝试增大A的元素,故i右移
- 当A[i] + B[j] == x时,记录当前组合
这种方法的正确性基于单调性原理:由于数组有序,i右移必然增大和,j左移必然减小和。算法时间复杂度降为O(n),因为i和j总共移动次数不超过n+m次。
3. 详细实现与代码解析
3.1 C++标准实现
cpp复制#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main() {
int n, m, x;
cin >> n >> m >> x;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < m; i++) cin >> b[i];
for (int i = 0, j = m - 1; i < n; i++) {
while (j >= 0 && a[i] + b[j] > x) j--;
if (a[i] + b[j] == x) {
cout << i << " " << j << endl;
break;
}
}
return 0;
}
3.2 关键代码逻辑说明
- 初始化阶段:读取两个数组的长度n,m和目标值x
- 双指针设置:i从0开始(A的最小值),j从m-1开始(B的最大值)
- 核心循环:固定i,调整j的位置直到和≤x
- 终止条件:找到第一个满足条件的对即可返回(根据题目要求)
注意:题目保证解存在,所以内层while循环结束后无需额外判断j的合法性
4. 算法正确性证明
我们可以用循环不变式来证明算法的正确性:
初始化:当i=0时,j会移动到满足a[0]+b[j]≤x的最大位置。如果等于x则找到解,否则说明所有包含a[0]的组合都小于x。
保持:假设对于i=k时成立,当i=k+1时,由于数组有序,a[k+1]≥a[k],因此只需要从当前j位置继续左移即可,不需要回溯。
终止:当i遍历完或j越界时终止,由于题目保证解存在,所以必定会在循环体内找到解。
5. 复杂度分析与优化对比
5.1 时间复杂度
- 暴力解法:O(n²)
- 双指针法:O(n+m)
当n=m=1e5时:
- 暴力:1e10次操作(约10秒)
- 双指针:2e5次操作(约1毫秒)
5.2 空间复杂度
两种方法都是O(1)的额外空间(不计输入存储),但双指针法避免了不必要的计算。
6. 边界条件与注意事项
- 数组为空的情况:题目保证n,m≥1,实际工程中需要额外判断
- 多个解的情况:当前代码只返回第一个解,如需所有解需继续搜索
- 无解的情况:虽然题目保证有解,实际应用需要处理无解场景
- 大数据测试:建议用最大边界值测试(如1e5个元素)
- 元素重复的情况:算法仍然适用,但返回的解可能不唯一
重要技巧:可以通过在移动指针时打印调试信息来观察算法执行过程:
cpp复制cout << "i=" << i << " j=" << j << " sum=" << a[i]+b[j] << endl;
7. 算法扩展与应用
7.1 三数之和问题
类似思路可以解决LeetCode 15题三数之和:先固定一个数,然后对剩余部分用双指针法处理,时间复杂度从O(n³)降到O(n²)。
7.2 实际工程应用
- 电商价格组合:从商品池A和配件池B中寻找总价等于优惠券面额的组合
- 游戏装备搭配:从武器库和防具库中寻找满足战力要求的装备组合
- 资源调度:从两个资源池中选取满足总量要求的资源组合
8. 不同语言实现对比
8.1 Python实现
python复制n, m, x = map(int, input().split())
a = list(map(int, input().split()))
b = list(map(int, input().split()))
i, j = 0, m - 1
while i < n and j >= 0:
s = a[i] + b[j]
if s == x:
print(i, j)
break
elif s > x:
j -= 1
else:
i += 1
8.2 Java实现
java复制import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt(), x = sc.nextInt();
int[] a = new int[n], b = new int[m];
for (int i = 0; i < n; i++) a[i] = sc.nextInt();
for (int i = 0; i < m; i++) b[i] = sc.nextInt();
for (int i = 0, j = m - 1; i < n; i++) {
while (j >= 0 && a[i] + b[j] > x) j--;
if (a[i] + b[j] == x) {
System.out.println(i + " " + j);
break;
}
}
}
}
9. 常见错误与调试技巧
9.1 典型错误案例
-
指针移动方向错误:
cpp复制// 错误:当和大于x时应该减小j而不是增加i while (j >= 0 && a[i] + b[j] > x) i++; // 错误写法 -
边界条件遗漏:
cpp复制// 忘记检查j的合法性 if (a[i] + b[j] == x) // 可能越界 -
初始化错误:
cpp复制int j = m; // 应该为m-1
9.2 调试方法
- 小数据测试:用n=m=3的小数组手动验证
- 打印指针轨迹:输出每次循环的i,j和当前和
- 极端值测试:如x非常大或非常小的情况
- 相同元素测试:验证算法在重复元素时的表现
10. 性能优化进阶
对于特别大的数据(如n=1e7),可以考虑以下优化:
- 循环展开:减少循环开销
- 输入优化:使用快速读取方法
cpp复制ios::sync_with_stdio(false); cin.tie(0); - 并行化:将数组分块处理(需要修改算法逻辑)
在实际测试中,对于n=m=1e5的数据规模,优化后的C++代码可以在约50ms内完成计算,而Python版本可能需要500ms左右,体现了语言性能差异。
