一、两数之和 II - 输入有序数组(LeetCode 167)
题目描述
给你一个 非递减排序 的整数数组 numbers,请你从数组中找出两个数,使它们的和等于目标数 target。
- 函数应该以长度为 2 的整数数组的形式返回这两个数的下标值(索引从 1 开始)。
- 输入有且只有一个解,且同一个元素不能被重复使用。
完整代码
java
class Solution {
public int[] twoSum(int[] numbers, int target) {
int n = numbers.length;
// 1. 指针初始化:左指针头(0)、右指针尾(n-1)(对应之前的解题重点)
int left = 0;
int right = n - 1;
// 2. 双指针核心逻辑:left < right 避免指针交叉(剪枝基础)
while (left < right) {
int sum = numbers[left] + numbers[right];
// 剪枝:提前终止无效循环(可选,但提升效率)
// 若左指针元素 > target/2,后续和必然超过target(有序数组)
if (numbers[left] > target / 2) {
break;
}
if (sum > target) {
// 和太大:右指针左移(和变小,对应解题重点的移动逻辑)
right--;
} else if (sum < target) {
// 和太小:左指针右移(和变大)
left++;
} else {
// 3. 坑点:索引从1开始,返回[left+1, right+1]
return new int[]{left + 1, right + 1};
}
}
// 题目保证有唯一解,此处仅为语法兜底
return new int[]{-1, -1};
}
}
代码关键解释
- 指针初始化:严格对应「左头右尾」,利用有序数组的单调性;
- 剪枝优化 :
numbers[left] > target/2时,后续和必然超过 target(因为数组递增,right≥left,numbers [right]≥numbers [left]),直接 break; - 索引坑点 :最终返回
left+1/right+1,而非原数组的 0 开始索引; - 时间 / 空间复杂度:O (n) / O (1),满足题目「常量级额外空间」要求。
二、三数之和(LeetCode 15)
题目描述
给你一个整数数组 nums,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j != k,且 nums[i] + nums[j] + nums[k] = 0。
- 要求:返回所有和为 0 且不重复的三元组(不能有重复的组合)。
- 数组可能包含重复元素,需手动去重。
完整代码
java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
int n = nums.length;
if (n < 3) {
return res; // 剪枝:元素不足3个,直接返回空
}
// 1. 先排序(核心前提:有序才能双指针+剪枝+去重)
Arrays.sort(nums);
// 2. 固定第一个数(k-2=1个固定数),转化为两数之和问题(对应解题模板)
for (int i = 0; i < n - 2; i++) {
// 剪枝1:固定数>0,后续和必然>0(有序数组),直接break
if (nums[i] > 0) {
break;
}
// 去重1:固定数去重(避免重复组合,对应去重规则)
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// 3. 双指针初始化:left=i+1(避免重复使用i)、right=n-1
int left = i + 1;
int right = n - 1;
while (left < right) { // 剪枝:指针不交叉
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
// 和太大:右指针左移(和变小)
right--;
} else if (sum < 0) {
// 和太小:左指针右移(和变大)
left++;
} else {
// 找到合法组合,加入结果
res.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重2:左指针去重(跳过重复元素,避免重复组合)
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
// 去重3:右指针去重
while (left < right && nums[right] == nums[right - 1]) {
right--;
}
// 4. 移动指针:找到解后,双指针同时收缩(避免死循环)
left++;
right--;
}
}
}
return res;
}
}
代码关键解释(对应之前的剪枝 / 去重规则)
- 排序前提 :无序数组无法双指针 / 剪枝 / 去重,必须先
Arrays.sort(nums); - 剪枝逻辑 :
- 元素不足 3 个:直接返回空(无效输入);
- 固定数
nums[i]>0:有序数组后续数更大,和不可能为 0,直接 break; - 指针交叉
left >= right:终止内层循环(双指针基础剪枝);
- 去重逻辑 :
- 固定数去重:
i>0 && nums[i]==nums[i-1]→ continue(跳过重复的固定数); - 左指针去重:找到解后,
while(left<right && nums[left]==nums[left+1])→ left++; - 右指针去重:找到解后,
while(left<right && nums[right]==nums[right-1])→ right--; - ❗ 关键:去重必须在「找到合法组合后」执行,且用
left++/right--(不能用 continue,否则死循环);
- 固定数去重:
- 核心转化 :固定
i后,问题转化为「在[i+1, n-1]找两个数,和为-nums[i]」(两数之和的变种); - 时间复杂度:O (n²)(排序 O (n log n) + 双层循环 O (n²)),空间复杂度 O (log n)(排序的系统栈空间)。
总结:两道题的核心关联与差异
| 维度 | 两数之和 II | 三数之和 |
|---|---|---|
| 数组状态 | 输入已排序 | 需手动排序 |
| 双指针角色 | 直接头 + 尾双指针 | 外层固定 1 数,内层头 + 尾双指针 |
| 去重需求 | 无(输入唯一解) | 必须去重(避免重复组合) |
| 剪枝重点 | 左指针 > target/2 直接 break | 固定数 > 0 直接 break |
| 结果形式 | 返回索引(从 1 开始) | 返回不重复的数值组合 |
两道题的核心思想完全一致:利用有序性 + 双指针缩小范围,区别仅在于「是否需要固定数」「是否需要去重」------ 掌握这个核心,所有「有序数组找 k 数之和」的题都能套用模板解决。