0167-两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers
,该数组已按 ** __ 非递减顺序排列 **,请你从数组中找出满足相加之和等于目标数target
的两个数。如果设这两个数分别是 numbers[index1]
和 numbers[index2]
,则 1 <= index1 < index2 <= numbers.length
。
以长度为 2 的整数数组 [index1, index2]
的形式返回这两个整数的下标 index1
__ 和 __index2
。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1:
**输入:** numbers = [ ** _2_** , ** _7_** ,11,15], target = 9
**输出:** [1,2]
**解释:** 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
示例 2:
**输入:** numbers = [ ** _2_** ,3, ** _4_** ], target = 6
**输出:** [1,3]
**解释:** 2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
示例 3:
**输入:** numbers = [ ** _-1_** , ** _0_** ], target = -1
**输出:** [1,2]
**解释:** -1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
提示:
2 <= numbers.length <= 3 * 104
-1000 <= numbers[i] <= 1000
numbers
按 非递减顺序 排列-1000 <= target <= 1000
- 仅存在一个有效答案
解题思路:
很多人做这个题目想不到正确的 $O(n)$ 解法,即使看了答案理解了,下次再做的时候还是会忘记。要想真正理解这道题,就要明白解法背后的道理。这样不仅可以记住这道题,还能举一反三解决类似的题目。
很多题解只给出了双指针解法的代码,但没有说明解法的正确性。为什么双指针往中间移动时,不会漏掉某些情况呢?要解答这个问题,我们要从 缩减搜索空间 的角度思考这个解法。下面我将以文字和图片两种方式进行讲解。
首先放上参考答案:
1 | vector<int> twoSum(vector<int>& numbers, int target) { |
1 | public int[] twoSum(int[] numbers, int target) { |
需要注意的是,虽然本题叫做 Two Sum II,但解法和 Two Sum 完全不同。
图解双指针解法的原理
在这道题中,我们要寻找的是符合条件的一对下标 $(i, j)$,它们需要满足的约束条件是:
- $i$、$j$ 都是合法的下标,即 $0 \le i < n, 0 \le j < n$
- $i < j$(题目要求)
而我们希望从中找到满足 A[i] + A[j] == target
的下标 $(i, j)$。以 $n = 8$ 为例,这时候全部的搜索空间是:
{:width=400}
{:align=center}
由于 $i$、$j$ 的约束条件的限制,搜索空间是白色的倒三角部分。可以看到,搜索空间的大小是 $O(n^2)$ 数量级的。如果用暴力解法求解,一次只检查一个单元格,那么时间复杂度一定是 $O(n^2)$。要想得到 $O(n)$ 的解法,我们就需要能够一次排除多个单元格。那么我们来看看,本题的双指针解法是如何削减搜索空间的:
一开始,我们检查右上方单元格 $(0, 7)$,即计算 A[0] + A[7]
,与 target
进行比较。如果不相等的话,则要么大于 target
,要么小于 target
。
{:width=400}
{:align=center}
假设此时 A[0] + A[7]
小于 target
。这时候,我们应该去找和更大的两个数。由于 A[7]
已经是最大的数了,其他的数跟 A[0]
相加,和只会更小。也就是说 A[0] + A[6]
、A[0] + A[5]
、……、A[0] + A[1]
也都小于 target
,这些都是不合要求的解,可以一次排除。这相当于 $i=0$ 的情况全部被排除。对应用双指针解法的代码,就是 i++
,对应于搜索空间,就是削减了一行的搜索空间,如下图所示。
{:width=400}
{:align=center}
排除掉了搜索空间中的一行之后,我们再看剩余的搜索空间,仍然是倒三角形状。我们检查右上方的单元格 $(1, 7)$,计算 A[1] + A[7]
与 target
进行比较。
{:width=400}
{:align=center}
假设此时 A[0] + A[7]
大于 target
。这时候,我们应该去找 和更小的两个数。由于 A[1]
已经是当前搜索空间最小的数了,其他的数跟 A[7]
相加的话,和只会更大。也就是说 A[1] + A[7]
、A[2] + A[7]
、……、A[6] + A[7]
也都大于 target
,这些都是不合要求的解,可以一次排除。这相当于 $j=0$ 的情况全部被排除。对应用双指针解法的代码,就是 j++
,对应于搜索空间,就是削减了一列的搜索空间,如下图所示。
{:width=400}
{:align=center}
可以看到,无论 A[i] + A[j]
的结果是大了还是小了,我们都可以排除掉一行或者一列的搜索空间。经过 $n$ 步以后,就能排除所有的搜索空间,检查完所有的可能性。搜索空间的减小过程如下面动图所示:
{:width=400}
{:align=center}
实际上还有几道题也是用到了这样的 缩减搜索空间 的思想: