2 条题解
-
0
你好!我是你的OI教练。
这道题虽然标题提到了“栈和队列”,但实际上这是一个美丽的误会。这道题最经典、最高效的解法并不需要栈或队列,而是用一种非常巧妙的双指针技巧。不过,我也会解释为什么人们可能会联想到栈和队列,以及如何用它们“强行”解决这个问题。
第一部分:最优解法 —— 双指针 (Two Pointers)
这才是这道题的正解,也是面试和竞赛中的标准解法。它利用了数组**“升序排序”**这个关键信息。
a. 思路提示 (启发式教学)
拿出你的草稿纸,画出这个数组:
1 2 4 7 11 15,目标和是15。-
设置起点:我们设置两个“指针”(其实就是数组下标),一个指向数组的开头(
left),一个指向结尾(right)。1 2 4 7 11 15 ^ ^ left right -
第一次尝试:计算
arr[left] + arr[right],也就是1 + 15 = 16。- 思考:
16比我们的目标15大了。我们应该怎么调整指针来让和变小一点? left指向的是最小的数,right指向的是最大的数。- 如果我把
left往右移,和只会变得更大。 - 所以,唯一的办法是把
right向左移动,换一个稍小一点的数。
- 思考:
-
第二次尝试:
left不动,right左移一位。1 2 4 7 11 15 ^ ^ left right计算
arr[left] + arr[right],也就是1 + 11 = 12。- 思考:
12比我们的目标15小了。我们应该怎么调整指针来让和变大一点? - 如果我把
right再往左移,和只会更小。 - 所以,唯一的办法是把
left向右移动,换一个稍大一点的数。
- 思考:
-
第三次尝试:
left右移一位,right不动。1 2 4 7 11 15 ^ ^ left right计算
arr[left] + arr[right],也就是2 + 11 = 13。- 思考:
13还是比15小。继续把left向右移动。
- 思考:
-
第四次尝试:
left右移一位,right不动。1 2 4 7 11 15 ^ ^ left right计算
arr[left] + arr[right],也就是4 + 11 = 15。- 找到了! 和正好等于目标
15。输出4和11,程序结束。
- 找到了! 和正好等于目标
b. 算法总结
- 初始化
left = 0,right = n - 1。 - 当
left < right时,循环: a. 计算sum = arr[left] + arr[right]。 b. 如果sum == target,恭喜你找到了,输出arr[left]和arr[right],然后break。 c. 如果sum < target,说明和太小了,需要增大,所以left++。 d. 如果sum > target,说明和太大了,需要减小,所以right--。
时间复杂度:
left和right指针都只从头到尾(或从尾到头)扫了一遍,所以复杂度是 ,非常高效。
第二部分:为什么会提到“栈和队列”?
这可能是出题人想考察你对数据结构基本特性的理解,或者这是一个思维陷阱。
- 栈 (Stack):特性是后进先出 (LIFO)。
- 队列 (Queue):特性是先进先出 (FIFO)。
你看,
left指针从左到右移动,像不像一个队列的出队操作?right指针从右到左移动,像不像一个特殊的栈(把数组反过来看)的出栈操作?双指针算法的移动方式,确实和这两种数据结构的“访问头部/尾部”的行为有相似之处。我们可以用它们来模拟双指针,但这会把简单问题复杂化。
a. 用栈和队列“强行”求解 (不推荐)
这是一种为了用而用的解法,不实用,但可以帮你理解数据结构。
- 把数组中的所有元素都压入一个栈
s和一个队列q。 s.top()就是最大的元素(对应right指针)。q.front()就是最小的元素(对应left指针)。- 循环:
a.
sum = s.top() + q.front()。 b. 如果sum == target,找到答案。 c. 如果sum < target,q.pop()(相当于left++)。 d. 如果sum > target,s.pop()(相当于right--)。
缺点:
- 需要 的额外空间来存储栈和队列。
- 代码比双指针更长,也更慢(因为数据结构的维护有开销)。
- 完全没有必要。
第三部分:代码实现
这里只给出最高效、最正确的双指针解法。
#include <iostream> #include <vector> #include <sstream> // 用于处理单行不定长输入 using namespace std; // 解决问题的核心函数 void find_two_sum(const vector<int>& nums, int target) { if (nums.size() < 2) { // 数组元素少于2个,不可能找到 return; } int left = 0; int right = nums.size() - 1; while (left < right) { int current_sum = nums[left] + nums[right]; if (current_sum == target) { // 找到了,输出结果 cout << nums[left] << " " << nums[right] << endl; return; // 找到一对即可退出 } else if (current_sum < target) { // 和太小,移动左指针以增大和 left++; } else { // current_sum > target // 和太大,移动右指针以减小和 right--; } } // 如果循环结束还没找到,可以输出提示(题目没要求) // cout << "No pair found." << endl; } int main() { // 优化IO ios::sync_with_stdio(false); cin.tie(0); // --- 处理输入 --- // 读取第一行不定长的数组 (这是本题的第一个难点,stringstream和getline配合可以方便地处理) string line; getline(cin, line); stringstream ss(line); vector<int> nums; int num; while (ss >> num) { nums.push_back(num); } // 读取第二行的目标值 int target; cin >> target; // --- 调用算法 --- find_two_sum(nums, target); return 0; }教练总结: 这道题是双指针算法的完美应用范例。一定要牢记它的使用前提——数组有序。它通过从两端向中间“夹逼”的方式,在 时间内解决问题。虽然可以用栈和队列模拟,但这并非问题的本质解法。
-
-
0
C++ :
#include<cstdio> int a[10000],d; int main() { int i,j,m,s,flag=1; char c; d=0; while(scanf("%d%c",&m,&c)) { a[d++]=m; if(c=='\n'||c=='\r') break; } scanf("%d",&s); i=0; while(i<d&&flag) { for(j=i+1;;j++) { if(a[i]+a[j]==s) { printf("%d %d\n",a[i],a[j]); flag=0; break; } if(a[i]+a[j]>s) break; } i++; } return 0; }
- 1
信息
- ID
- 2427
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- (无)
- 标签
- (无)
- 递交数
- 0
- 已通过
- 0
- 上传者