题目描述:(力扣题库1)
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标(构造方法)。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
class Solution {
public int[] twoSum(int[] nums, int target) {
}
}
示例 1:
输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示 2:
输入:nums = [3,2,4], target = 6 输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6 输出:[0,1]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
- 只会存在一个有效答案
法一: 暴力枚举法
思路分析: 将nums数组内的每个数,两两相加,直到两个两个元素的和等于target
具体步骤如下:
- 外层循环遍历数组中的每一个元素,假设当前元素的索引为i。
- 内层循环从当前元素的下一个位置开始遍历数组,假设内层循环的索引为j。
- 在内层循环中,检查当前元素nums[i]与内层循环中的元素nums[j]的和是否等于目标值target。
- 如果找到两个元素的和等于目标值,则返回它们的索引[i, j]。
- 如果内层循环结束后仍未找到符合条件的元素对,继续外层循环,直到找到答案或遍历完整个数组。
这种方法的时间复杂度为O(n^2),因为需要遍历所有可能的组合。虽然这种方法简单直观,但对于较大规模的数组来说,效率较低。
class Solution {
public int[] twoSum(int[] nums, int target) {
for(int i = 0; i < nums.length; i++){
for(int j = i + 1; j < nums.length; j++){
if(nums[i] + nums[j] == target)
return new int[] {i, j};
}
}
return null;
}
}
进阶:你可以想出一个时间复杂度小于 O(n2)
的算法吗?
法二: 哈希表查数:
思路分析: 假设,存在一个表格key,用来记录target 与 数组nums内的元素的差值 target - nums[i](并记录nums数组元素的下标); 遍历数组nums, 当nums[j] 与 key表中的某个值(target - nums[i])相等,即nums[j] + nums[i] = target,返回i, j. 时间复杂度为o(n).其中, 关键点为: 判断nums数组内的元素是否与key表内的值相等,以及确定下标i, 如果把nums[i]与key表中的每个值比较,不就又变成了暴力枚举?所以,我们可以利用哈希表,利用其特性快速判断nums数组内的元素是否与key表内的值相等,以及确定下标i.
那么,接下来是代码演示:
第一步,创建一个哈希表,存放nums数组内的元素以及下标i
Map<Integer, Integer> hasMap = new HashMap<>(nums.length - 1);
第二步, 将nums第一个元素,及下标导入哈希表,遍历nums数组,如果nums[i]与哈希表中的一个值相等,返回两个下标,如果不是,则将nums[i] 与 i导入哈希表
hasMap.put(nums[0], 0);
for (int i = 1; i < nums.length; i++) {
if(hasMap.containsKey(target - nums[i])){
return new int[] {i, hasMap.get(target - nums[i])};
}
hasMap.put(nums[i], i);
}
return null; // 如果一直没有匹配的,返回null
这里,我并没有一开始直接把哈希表创建出来,而是在遍历nums数组的过程中创建的
代码到这,已经结束了,以下是该方法的完整代码(已在leetcode通过)
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> hasMap = new HashMap<>(nums.length - 1);
hasMap.put(nums[0], 0);
for (int i = 1; i < nums.length; i++) {
if(hasMap.containsKey(target - nums[i])){
return new int[] {i, hasMap.get(target - nums[i])};
}
hasMap.put(nums[i], i);
}
return null;
}
}
最后,回到当初创建哈希表时提出的疑问
在这段代码中,哈希表的长度被初始化为`nums.length - 1`的原因是为了避免哈希表在存储元素时发生扩容,从而提高代码执行的效率。在Java中,`HashMap`的默认负载因子(load factor)为0.75,这意味着当哈希表中的元素数量达到容量的75%时,哈希表会自动扩容,将容量翻倍。
在这段代码中,我们知道`hasMap`哈希表的目的是为了存储数组`nums`中的元素,以便快速查找与当前元素配对的另一个元素。由于在for循环中,我们从`nums`数组的第一个元素开始遍历,所以在初始化哈希表时,我们只需要考虑从第二个元素开始的元素。
如果我们将哈希表的长度设置为`nums.length`,那么哈希表的初始容量会是`nums.length`,这会导致在向哈希表中添加元素时,可能会触发多次的哈希表扩容操作,从而降低代码的执行效率。
因此,为了避免不必要的哈希表扩容操作,我们选择将哈希表的长度初始化为`nums.length - 1`,这样可以在一定程度上减少哈希表的扩容次数,提高代码的执行效率。
以上就是本篇文章的全部内容,博主也是最近才接触哈希表这一类型,我将自己当时遇到的疑惑(例如: 为什么哈希表长度为 nums.length - 1, 而不是nums.length),以及我想到的问题,以讲解的方式,在文章内回答了. 所以,讲解部分的内容可能有点繁琐,如诺有不妥之处,敬请谅解.