两数之和
给定一个整数数组nums和一个整数目标值target,请你在该数组中找出
和为目标值
target的那两个
整数,并返回它们的数组下标。
数组中同一个元素在答案中不能重复出现,即返回的两个下标不能相同
方法一:暴力枚举
可以先定位一个数,然后让其它数都和它相加,判断结果是否为target,如果是则将下标存入数组返回,如果不是则继续遍历,直到找到这两个符合条件的数或者全部遍历完毕。
那么这种方法用代码实现需要用到两层for循环嵌套,外层定位一个数字,内层遍历数组元素判断是否符合条件
代码实现
class Solution {
public int[] twoSum(int[] nums, int target) {
// 定义一个两个元素的数组来存储目标数字的下标
int[] arr = 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) {
arr[0] = i;
arr[1] = j;
}
}
}
return arr;
}
}
复杂度分析
时间复杂度:O(n^2)
- 每次for循环的时间复杂度为O(n),则两层循环的时间复杂度为O(n^2)
空间复杂度:O(1)
- 每次遍历一个元素,空间复杂的为O(1)
可以看到,这种方法是比较容易想到和实现的,但是时间复杂度较高,而且暴力且无脑。。。
我们想要搞一个更快,更高级的算法,来彰显我们的学渣身份😎
方法二:哈希表
先分析方法一,时间复杂度高是因为寻找 nums[j] 的时间复杂度太高,那么就需要减少这个寻找时间来降低时间复杂度
延续这个思路,可以先创建一个空间X,每次从数组中取一个元素,判断X中是否有元素与之相加可以得到target,如果有,得到两个元素的下标,没有则将数组中取出的元素存入X中,然后继续寻找,直到数组中的数取完或找到符合条件的两个数
那么空间X该使用什么实现呢?
由于我们需要操作数组元素以及它们的下标,且下标与元素有映射关系,可以采用查找表法
查找表法通常有两种实现方式:
- 哈希表
- 平衡二叉搜索树
在本题中,我们不需要对表中元素进行排序,所以优选哈希表
OK,思路整理完毕,咱们直接上代码
Class Solution(){
public int[] twoSum(int[] nums, int targe){
// 先获取数组长度,方便确定遍历次数以及哈希表长度
int n = nums.length;
// 创建哈希表对象,并定义表长,防止表扩容造成空间浪费
Map<Integer, Integer> hashMap = new HashMap<>(n - 1);
// 遍历数组
for(int i = 0; i < n; i++) {
// 如果表中有数符合target - nums[i],则直接返回包含两个数的下标数组
if(hashMap.containsKey(target - nums[i])) {
return new int[] {hashMap.get(target - nums[i], i)};
}
// 如果这个数不满足条件,则存入表中,与之后数组中的数字进行条件判断
hashMap.put(nums[i], i);
}
// 如果没有符合条件的值,则返回空数组
return new int[0];
}
}
复杂度分析
时间复杂度:O(n)
- 因为只是用了一次for循环,通过hashMap.containsKey(target - nums[i])方法实现了不用再次循环遍历表中元素就可以判断所有表中元素是否符合条件,从而减少了时间复杂度
时间复杂度:O(n)
- 这里空间复杂度增加主要是因为新建了哈希表空间,并且哈希表最多需要存n-1个键值对,因此空间复杂度是O(n)
总结
相比之下,方法一所需时间更长,但占用空间较少,并且算法思路简单易懂。方法二则降低了时间的花费,但占用了更多空间,也就是牺牲空间换取了时间,但是思路没有方法一简单明了。
但是在日常工作和使用中,大家一般更看重时间的利用,所以如果没有特别要求,我们一般首选时间复杂度较低的算法。
PS:本题摘自LeetCode,网站上有讲解视频,觉得看文字不好理解的朋友可以去看看视频加深理解
视频链接:1. 两数之和 - 力扣(Leetcode)