目录
给定一个升序排列的整数数组 nums ,从数组中找出两个数满足相加之和等于目标数 target 。
假设每个输入只对应唯一的答案,而且不可以重复使用相同的元素。
返回两数的下标值,以数组形式返回。
暴力解法
public int[] twoSum (int[] nums, int target) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return new int[0];
}
方法的逻辑如下:
获取数组
nums
的长度n
。使用两层嵌套循环来遍历数组中的元素对:
- 外层循环变量
i
从0
遍历到n-1
。- 内层循环变量
j
从i+1
遍历到n
。在内层循环中检查元素
nums[i]
和nums[j]
是否满足和为target
的条件:
- 如果
nums[i] + nums[j] == target
,则说明找到了满足条件的一对元素。此时,方法返回一个包含这两个元素索引的数组,即new int[]{i, j}
。如果两层循环结束后没有找到满足条件的元素对,则方法返回一个空数组
new int[0]
。
这种方法的时间复杂度是 O(n^2),其中 n 是数组
nums
的长度。由于这种方法没有使用任何优化,所以当数组很大时可能会非常慢。
哈希表解法
将数组的值作为key存入map,target - num作为key
public int[] twoSumMap (int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]), i};
}
map.put(nums[i], i);
}
return new int[0];
}
这个方法同样接收一个整型数组
nums
和一个整数target
作为参数,目的是找出数组中两个数的和为target
的一对元素,并返回这两个元素的索引。方法的逻辑如下:
创建一个
HashMap
,用于存储数组元素的值和对应的索引。遍历数组
nums
:
- 对于每个元素
nums[i]
,计算complement
,即target - nums[i]
。- 检查
map
中是否已经存在complement
作为键(key)的值,如果存在,说明之前已经遇到了一个数,与当前的nums[i]
相加等于target
:
- 如果找到了,那么
map.get(complement)
就会给出之前数的索引,i
就是当前数的索引。这时,方法返回一个包含这两个索引的数组,即new int[]{map.get(complement), i}
。- 如果
map
中不存在complement
,则将当前元素的值和索引作为键值对存入map
,即map.put(nums[i], i)
。如果遍历结束后没有找到满足条件的元素对,则方法返回一个空数组
new int[0]
。
这种方法利用了哈希表的高效键查找特性,将时间复杂度从暴力解法的 O(n^2) 降低到了 O(n),其中 n 是数组
nums
的长度。
二分查找解法
先固定一个值(从下标0开始),再用二分查找查另外一个值,找不到则固定值向右移动,继续二分查找
import java.util.Arrays;
public class Solution {
public int[] twoSumBinarySearch(int[] nums, int target) {
// 创建一个数组的副本并对其进行排序
int[] sortedNums = nums.clone();
Arrays.sort(sortedNums);
for (int i = 0; i < sortedNums.length; ++i) {
int complement = target - sortedNums[i];
int low = i + 1; // 从当前元素的下一个开始二分查找
int high = sortedNums.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (sortedNums[mid] == complement) {
// 找到了两个数,现在需要找到它们在原始数组中的索引
int index1 = findIndex(nums, sortedNums[i], -1);
int index2 = findIndex(nums, complement, index1);
return new int[]{index1, index2};
} else if (sortedNums[mid] > complement) {
high = mid - 1;
} else {
low = mid + 1;
}
}
}
// 如果没有找到满足条件的元素对,返回一个空数组
return new int[0];
}
// 辅助函数,用于在原始数组中找到特定值的索引
private int findIndex(int[] nums, int value, int excludeIndex) {
for (int i = 0; i < nums.length; i++) {
if (nums[i] == value && i != excludeIndex) {
return i;
}
}
return -1;
}
}
首先创建了原始数组的一个副本并对其进行排序,然后使用二分查找来寻找目标值的补数。一旦找到了这样的一对数,我们使用
findIndex
辅助函数在原始数组中找到它们的索引。注意,当找到第二个数的索引时,需要确保它不是第一个数的索引(如果两个数相同的话)。
对数组进行了排序,排序的时间复杂度通常是 O(n log n)
双指针(对撞指针)解法
使用两个指针分别指向数组的头部和尾部,通过移动这两个指针来找到和为 target
的两个数。
public int[] twoPoint (int[] nums, int target) {
int low = 0, high = nums.length - 1;
while (low < high) {
int sum = nums[low] + nums[high];
if (sum == target) {
return new int[]{low + 1, high + 1};
} else if (sum < target) {
++low;
} else {
--high;
}
}
return new int[]{-1, -1};
}
这个方法的逻辑如下:
初始化两个指针
low
和high
,分别指向数组的第一个元素和最后一个元素。在
low
小于high
的条件下进行循环:
- 计算当前两个指针指向的元素的和
sum
。- 如果
sum
等于target
,则找到了满足条件的一对元素,返回它们的索引。注意,索引是从1开始的,所以返回的是{low + 1, high + 1}
。- 如果
sum
小于target
,则需要增加和的值,所以low
指针右移(即++low
)。- 如果
sum
大于target
,则需要减少和的值,所以high
指针左移(即--high
)。如果循环结束还没有找到满足条件的元素对,返回
{-1, -1}
作为标志,表示没有找到。
时间复杂度为 O(n),因为它只需要一次遍历数组。然而,这个方法的前提是数组必须是有序的。