力扣解题-167. 两数之和 II - 输入有序数组

6 阅读6分钟

力扣解题-167. 两数之和 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

仅存在一个有效答案

Related Topics

数组、双指针、二分查找


第一次解答(超时)

解题思路

核心方法:暴力双指针遍历(低效版),采用“左指针固定,右指针从左指针下一位开始逐个右移”的暴力遍历方式,未利用数组“非递减排列”的核心特性,导致时间复杂度极高,最终超时。

核心逻辑拆解

该解法的思路是“穷举所有可能的两数组合”:

  1. 左指针left从0开始,右指针right初始为left+1
  2. 计算两数之和sum
    • sum == target,返回[left+1, right+1](下标从1开始);
    • sum > target,左指针右移,右指针重置为left+1
    • sum < target,右指针右移,若右指针越界则左指针右移、右指针重置;
  3. 遍历所有组合直到找到答案。
超时原因分析
  • 时间复杂度:O(n²)(最坏情况需遍历n(n-1)/2个组合,n≤3×10⁴时,总操作数约4.5×10⁸,远超时间限制);
  • 核心问题:未利用数组“有序”的特性,暴力遍历所有组合,完全浪费了题目给出的关键条件;
  • 逻辑缺陷:当sum > target时仅移动左指针,忽略了“有序数组中右指针左移也能减小和”的关键逻辑,导致遍历路径冗余。
    public int[] twoSum(int[] numbers, int target) {
        int left=0;int right=1;
                while(left<right && left<numbers.length){
                    int sum=numbers[left]+numbers[right];
                    if(sum==target){
                        return new int[]{left+1,right+1};
                    }
                    if(sum>target){
                        left++;
                        right=left+1;
                    }
                    if(sum<target){
                        right++;
                        if(right>=numbers.length){
                            left++;
                            right=left+1;
                        }
                    }
                }
                return new int[]{};
    }

第二次解答

解题思路

核心方法:首尾双指针法(最优解),利用数组“非递减排列”的特性,从首尾两端向中间逼近目标和,时间复杂度O(n)、空间复杂度O(1),完全满足题目“常量级额外空间”的要求,是本题的最优解法。

核心逻辑拆解(通俗版)

有序数组的核心特性是“左边小、右边大”,因此可以通过调整首尾指针的位置,精准控制两数之和的大小:

  1. 左指针left初始为0(数组最左端,最小值),右指针right初始为numbers.length-1(数组最右端,最大值);
  2. 计算两数之和sum
    • sum == target:找到唯一答案,返回[left+1, right+1](下标从1开始);
    • sum < target:当前和偏小,需要增大和 → 左指针右移(取更大的数);
    • sum > target:当前和偏大,需要减小和 → 右指针左移(取更小的数);
  3. 循环直到left < right(保证两个指针不重合、不重复使用元素),题目保证有唯一解,因此循环内必能找到答案。
具体步骤(以示例1 numbers=[2,7,11,15],target=9为例)
  1. 初始:left=0(2),right=3(15)→ sum=17 > 9 → right左移至2(11);
  2. sum=2+11=13 >9 → right左移至1(7);
  3. sum=2+7=9 ==9 → 返回[0+1,1+1]=[1,2]。
性能优势
  • 时间复杂度:O(n)(最多遍历数组一次,每个元素仅被访问一次),耗时1ms击败99.89%用户;
  • 空间复杂度:O(1)(仅使用两个指针变量,无额外数组/哈希表开销);
  • 逻辑优势:
    1. 利用有序特性避免暴力遍历,效率呈指数级提升;
    2. 严格满足题目“常量级额外空间”的要求;
    3. 题目保证唯一解,无需处理多解或无解的边界情况。
    public int[] twoSum(int[] numbers, int target) {
        int left=0;
        int right=numbers.length-1;
        while (left<right){
            int sum=numbers[left]+numbers[right];
            if(sum==target){
                return new int[]{left+1,right+1};
            }else if(sum<target){
                left++;
            }else {
                right--;
            }
        }
        return new int[]{};
    }

示例解答

解题思路

解法1:二分查找法(备选高效解)

核心方法:固定单值 + 二分查找补值,遍历数组中的每个元素numbers[i],计算需要匹配的补值complement = target - numbers[i],然后在i+1到数组末尾的范围内二分查找该补值,时间复杂度O(n×logn)、空间复杂度O(1),效率略低于首尾双指针,但也是符合题目要求的高效解法。

核心逻辑拆解
  1. 遍历数组中的每个下标i(0 ≤ i < numbers.length-1);
  2. 计算补值complement = target - numbers[i]
  3. [i+1, numbers.length-1]范围内二分查找complement
    • 若找到,返回[i+1, mid+1](mid为补值的下标);
    • 若未找到,继续遍历下一个i
  4. 题目保证有唯一解,因此必能找到。
代码实现
public int[] twoSum(int[] numbers, int target) {
    int n = numbers.length;
    for (int i = 0; i < n; i++) {
        int complement = target - numbers[i];
        // 二分查找范围:i+1 到 n-1
        int left = i + 1, right = n - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2; // 避免溢出
            if (numbers[mid] == complement) {
                return new int[]{i + 1, mid + 1};
            } else if (numbers[mid] < complement) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
    }
    return new int[]{};
}
优势与适用场景
  • 时间复杂度:O(n×logn)(遍历O(n) + 每次二分O(logn)),效率低于双指针但远高于暴力法;
  • 适用场景:若题目未明确数组有序,但允许使用O(1)空间,可先排序再用双指针,或直接用此方法;
  • 逻辑价值:二分查找是有序数组的经典操作,该解法可拓展到“多值匹配”“范围查找”等场景。

总结

  1. 暴力双指针法(第一次解答):未利用有序特性,O(n²)时间复杂度导致超时,仅适合理解基础思路;
  2. 首尾双指针法(第二次解答):最优解,O(n)时间+O(1)空间,完全贴合题目要求,是面试/工程首选;
  3. 二分查找法(备选解):O(n×logn)时间+O(1)空间,效率略低但逻辑通用,适合拓展学习;
  4. 关键技巧:
    • 有序数组的两数之和问题,优先使用首尾双指针法,利用“左小右大”特性精准调整和的大小;
    • 避免暴力遍历,充分利用题目给出的“有序”“唯一解”等关键条件;
    • 下标从1开始的要求:返回结果时需将指针下标+1,是本题的易出错点。