排序算法之:利用“双指针法”对“已排序数组中的两数之和”问题进行高效求解
字数 2501 2025-12-23 02:49:25
排序算法之:利用“双指针法”对“已排序数组中的两数之和”问题进行高效求解
题目描述
给定一个已经按升序排列的整数数组 nums,以及一个目标值 target。请你找出数组中两个不同的元素,使得它们的和等于 target,并返回这两个元素的索引(通常假设索引从 1 开始)。题目保证有且只有一个有效答案。
例如:
- 输入:
nums = [2, 7, 11, 15],target = 9 - 输出:
[1, 2](因为nums[1] + nums[2] = 2 + 7 = 9)
本题的核心在于利用数组已排序的特性,设计出比暴力枚举更高效的算法。
解题过程
第一步:暴力解法分析(作为起点和对比)
最直观的想法是使用两层循环,枚举所有可能的数对 (i, j)(其中 i < j),检查它们的和是否为 target。
- 时间复杂度:O(n²),其中 n 是数组长度。
- 空间复杂度:O(1)。
这种方法没有利用数组已排序的性质,效率低下,但思路简单,有助于我们理解问题。
第二步:引入关键观察——已排序数组的性质
由于数组是升序排列的,数组中的元素具有以下性质:
- 如果固定一个较小的数
nums[i],那么要想和达到target,另一个数必须是一个较大的数nums[j]。 - 当我们从数组的两端同时向中间遍历时,可以通过比较当前和与
target的大小关系,智能地移动指针,从而避免不必要的枚举。
第三步:设计双指针算法
我们定义两个指针:
- 左指针
left:初始指向数组的第一个元素(最小元素),索引为 0。 - 右指针
right:初始指向数组的最后一个元素(最大元素),索引为n - 1。
算法步骤详解:
- 初始化:
left = 0,right = n - 1。 - 循环条件:当
left < right时,执行循环。因为需要两个不同的元素,所以指针不能相遇。 - 计算当前和:
current_sum = nums[left] + nums[right]。 - 比较与移动:
- 情况 A:如果
current_sum == target,我们找到了答案,直接返回[left + 1, right + 1](因为题目要求索引从1开始)。 - 情况 B:如果
current_sum < target,说明当前和太小了。为了让和变大,我们必须增大其中一个加数。由于数组是升序的,nums[left]已经是最小值(在当前候选范围内),所以唯一能变大的操作是将左指针left向右移动一位(left++),以选择一个更大的nums[left]。 - 情况 C:如果
current_sum > target,说明当前和太大了。为了让和变小,我们必须减小其中一个加数。nums[right]已经是最大值,所以唯一能变小的操作是将右指针right向左移动一位(right--),以选择一个更小的nums[right]。
- 情况 A:如果
- 重复步骤3和4,直到找到答案。
第四步:算法正确性证明(循环不变式思想)
为了让你彻底信服,我们用“循环不变式”来解释:
- 不变式:在每一次循环开始时,对于任何索引
i < left或j > right的元素,它们不可能是最终解的一部分。 - 初始化:在第一次循环前,
left=0,right=n-1。范围外的集合为空,不变式自然成立。 - 保持:
- 若和等于
target,算法终止并给出正确解。 - 若和小于
target,对于当前left,任何与right及right左边元素(因为它们更小或等于)的配对,和只会更小,所以nums[left]不可能与它们组成解。因此我们可以安全地将left右移,不变式得以保持。 - 若和大于
target,对于当前right,任何与left及left右边元素(因为它们更大或相等)的配对,和只会更大,所以nums[right]不可能与它们组成解。因此我们可以安全地将right左移,不变式得以保持。
- 若和等于
- 终止:由于每次循环至少移动一个指针,范围
[left, right]不断缩小。题目保证有解,所以循环必然在left < right的某一步终止于current_sum == target。
第五步:示例推演(以 nums = [2, 7, 11, 15], target = 9 为例)
- 初始:
left=0(值2),right=3(值15)。和=17 > 9。 - 和太大,
right--:left=0(2),right=2(11)。和=13 > 9。 - 和太大,
right--:left=0(2),right=1(7)。和=9 == 9。 - 找到答案,返回
[left+1, right+1] = [1, 2]。
第六步:复杂度分析
- 时间复杂度:O(n)。最坏情况下,两个指针从两端移动到中间,总共遍历了 n 个元素,每次操作是常数时间。
- 空间复杂度:O(1)。只使用了固定的额外空间(两个指针变量)。
第七步:边界条件与讨论
- 输入保证有解:如果题目没有此保证,循环终止条件可以是
left <= right,并在循环结束后返回一个表示未找到的值(如空数组)。 - 索引从0还是1开始:根据题目要求灵活调整返回结果。
- 数组元素可能为负数或零:该算法依然有效,因为排序性质是关键,数值正负不影响双指针的移动逻辑。
总结
这道题展示了如何巧妙地利用数据的有序性,将看似需要 O(n²) 时间的问题优化到 O(n)。双指针法是处理已排序数组相关问题(如两数之和、三数之和、移除元素等)的经典技巧,其核心在于通过比较和与目标值的关系,确定性地排除不可能的解,从而大幅减少搜索空间。