两数之和是LeetCode中算法的第一题,说简单也简单,说难也难,我截一个LeetCode热评的图
首先贴一下题目:
看到这道题的第一感觉,基本都是双层for循环,这个方法应该很容易想到,从第一个数开始循环,然后剩下的数放到另外一个循环中,看看两个循环中的数加起来会不会等于target,代码很简单,这个方法需要遍历两个循环,所以时间复杂度是O(n²)
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
for (int i = 0; i < nums.length - 1; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
result[0] = i;
result[1] = j;
return result;
}
}
}
return result;
}
提示最后给我们的进阶要求需要我们的时间复杂度小于O(n²),所以这肯定不是最优解
既然暴力求解的双层for循环不是最优解,那么有什么办法能优化一下这个方法吗?暴力求解因为最差的情况下需要把两个for循环都遍历完,所以如果我们能减少遍历的次数,是不是就可以优化一下这个方法了呢?
如果我们使用二分查找的话,那么遍历的次数就会减少,当然既然要使用二分查找的话,首先这个数组得是有序的,所以我们需要将数组先排个序
排完序后再次遍历这个数组,接下来就用二分查找的办法去寻找另外一个数,看看能不能找到相加结果为target的值,这个算法中有一个循环,并且有一个排序的操作,所以我们的时间复杂度是O(nlogn),这还不算完,即使找到了具体的两个数,还需要将它们分别在原数组中的index给找出来才能得出结果,总体来说有一些优化,但是实现起来有点小复杂
public int[] twoSum(int[] nums, int target) {
// clone一个数组出来再进行排序,因为我们还需要从原数组中取出我们要的两个数的index
int[] sortedNums = nums.clone();
Arrays.sort(sortedNums);
int[] result = new int[2];
// 遍历排序过的数组,看看能不能找到另外一个数,两数之和为target
for (int i = 0; i < sortedNums.length; i++) {
int index = findNumIndexInSortedArray(sortedNums, i + 1, target - sortedNums[i]);
if (index != -1) {
result [0] = getNumberIndex(nums, sortedNums[i], -1);
result [1] = getNumberIndex(nums, sortedNums[index], result[0]);
return result ;
}
}
return result;
}
// 将我们得到的数去原数组中寻找它的下标,并且两个下标不能相同(forbiddenIndex就是防止两个数下标一样)
public int getNumberIndex(int[] nums, int num, int forbiddenIndex) {
for (int i = 0; i < nums.length; i++) {
if (nums[i] == num && i != forbiddenIndex) {
return i;
}
}
return -1;
}
// 二分查找排序过的数组,看看数组中是否有一个数等于target - 数组中当前下标对应的值
public int findNumIndexInSortedArray(int[] sortedNums, int startIndex, int num) {
int leftIndex = startIndex;
int rightIndex = sortedNums.length - 1;
while (leftIndex <= rightIndex) {
int midIndex = (rightIndex - leftIndex) / 2 + leftIndex;
if (sortedNums[midIndex] == num) {
return midIndex;
} else if (sortedNums[midIndex] > num) {
rightIndex = midIndex - 1;
} else if (sortedNums[midIndex] < num) {
leftIndex = midIndex + 1;
}
}
return -1;
}
最后一种解法是使用map来解这个题目,这也是最优的解法。思路很简单,我们将数组的值作为key,下标作为value,使用我们的数组就可以得到一个map,然后遍历这个map,通过containsKey这个方法判断是否存在两个值相加等于target即可
public int[] twoSum(int[] nums, Integer target) {
int[] result = new int[2];
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], i);
}
for (int i = 0; i < nums.length; i++) {
// 依旧要保证下标不能相同
if (map.containsKey(target - nums[i]) && i != map.get(target - nums[i])) {
result[0] = i;
result[1] = map.get(target - nums[i]);
return result;
}
}
return result;
}
当然你也可以在LeetCode题解中找到更加精简的写法,省略了上面单独把数组转换map的操作,直接在for循环中添加map的值并进行搜索,总体的思路还是差不多的
public int[] twoSum(int[] nums, Integer target) {
Map<Integer, Integer> map = new HashMap<>();
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[2];
}
使用map最大的好处就是我们只需要通过最少一次的遍历就能找到我们想要的结果,所以时间复杂度是O(1),是这三种方法中最好的