【集合和映射】CSES - 两个数的和Sum of Two Values

题目描述

给定一个大小为 n n n 的整数数组,你需要找到两个不同位置上的数,它们的和等于给定的目标值 x x x。

输入格式

第一行包含两个整数 n , x n, x n,x ------ 数组的大小和目标和。

第二行包含 n n n 个整数 a 1 , a 2 , ... , a n a_1, a_2, \dots, a_n a1,a2,...,an ------ 数组的元素。

输出格式

输出两个整数,表示这两个数的位置(下标从 1 开始)。

如果有多个解,可以输出任意一个。

如果没有解,输出 IMPOSSIBLE

样例输入

cpp 复制代码
4 8
2 7 5 1

样例输出

cpp 复制代码
2 4

数据范围

  • 1 ≤ n ≤ 2 ⋅ 10 5 1 \le n \le 2 \cdot 10^5 1≤n≤2⋅105
  • 1 ≤ x , a i ≤ 10 9 1 \le x, a_i \le 10^9 1≤x,ai≤109

提交链接

Sum of Two Values

思路分析✨

朴素方法 🐢

最直接的想法是用两层循环,枚举所有数对:

  • 外层循环遍历第一个数 a [ i ] a[i] a[i]
  • 内层循环遍历第二个数 a [ j ] a[j] a[j]
  • 判断是否满足 a [ i ] + a [ j ] = x a[i] + a[j] = x a[i]+a[j]=x

这样时间复杂度是 O ( n 2 ) O(n^2) O(n2)。

但本题 n n n 最大可达 2 ⋅ 10 5 2 \cdot 10^5 2⋅105,那么 O ( n 2 ) O(n^2) O(n2) 大约是 4 ⋅ 10 10 4 \cdot 10^{10} 4⋅1010 次操作,显然会超时 ⏰。

所以必须寻找 更高效的算法。⚡

更高效的思路 💡

我们希望能在 一次遍历 中解决问题。

观察公式: a [ i ] + a [ j ] = x    ⟹    a [ j ] = x − a [ i ] a[i] + a[j] = x \implies a[j] = x - a[i] a[i]+a[j]=x⟹a[j]=x−a[i]

也就是说,在遍历数组时,对于每个元素 a [ i ] a[i] a[i],我们只需要知道 是否之前出现过某个数等于 x − a [ i ] x - a[i] x−a[i]

如果出现过,那么这两个数就是答案 ✅。

数据结构的选择 🛠️

为了快速判断「某个数是否出现过」,我们需要:

  • 哈希表 unordered_map :平均复杂度 O ( 1 ) O(1) O(1) 🏎️
  • 平衡二叉搜索树 map :复杂度 O ( log ⁡ n ) O(\log n) O(logn) 🐌

如下算法,选择 map<int,int> 来存储「值 → 位置」的映射。

算法流程 📑

  1. 输入 n , x n, x n,x 和数组 a a a。

  2. 初始化一个映射 m m m,用于存储「已经出现过的数」及其位置。

  3. 遍历数组下标 i i i:

    • 计算需要的补数: n e e d = x − a [ i ] need = x - a[i] need=x−a[i]

    • 如果 m m m 中存在 n e e d need need,说明找到一对数: a [ i ] + n e e d = x a[i] + need = x a[i]+need=x

      输出这两个数的位置 🎯。

    • 否则,将当前数存入 m m m,继续遍历。

  4. 如果遍历结束仍未找到,输出 IMPOSSIBLE


时间复杂度与空间复杂度 ⏳

  • 时间复杂度

    • 使用 map 时: O ( n log ⁡ n ) O(n \log n) O(nlogn)
    • 使用 unordered_map 时:期望 O ( n ) O(n) O(n)
  • 空间复杂度

    需要存储最多 n n n 个元素,复杂度为 O ( n ) O(n) O(n)。

总结 🎉

本题的关键在于利用「哈希 / 映射」来快速查找补数,从而将暴力解法的 O ( n 2 ) O(n^2) O(n2) 降到 O ( n log ⁡ n ) O(n \log n) O(nlogn) 或 O ( n ) O(n) O(n)。 只需遍历数组一次即可找到答案,保证了在大数据范围内的可行性。✅

😎 一句话总结

"边走边记,随时看看我缺的另一半是不是已经来过了。" ❤️

参考代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main()
{
	int n, x;
	cin >> n >> x;

	vector<int> a(n + 1);

	for (int i = 1; i <= n; i++)
		cin >> a[i];

	map<int, int> m;
	for (int i = 1; i <= n; i++)
	{
		if(m.count(x - a[i]))
		{
			cout << i << " " << m[x - a[i]];
			return 0;

		}
		m[a[i]] = i;
	}
	cout << "IMPOSSIBLE";
	return 0;
}