前言
在刚看到这个题目的时候大家是不是觉得非常简单呢?毕竟题目上都有“简单”的标签,但当大家看了之后就会发现这道题并没有想象中那么简单,不然社区里就不会有“有人相爱,有人夜里开车看海,有人leetcode第一题都做不出来。”这句名言了,哈哈哈。
但其实只要我们有一定的算法基础就不难看出来,在解决这道题之前,首先需要明确题目的要求和约束条件。题目要求给定一个整数数组 nums 和一个整数目标值 target,需要在数组中找出和为目标值 target 的两个整数,并返回它们的数组下标。同时,题目规定每种输入只会对应一个答案,但数组中同一个元素在答案里不能重复出现。针对这个问题,我们可以想到多种解决方法。一种常见的解法是利用哈希表,另一种是使用双指针。接下来,我们将分别介绍这两种解法的思路和实现过程,并比较它们的优缺点。
哈希表解决方法
我们可以利用哈希表来存储已经遍历过的数字以及它们的索引,以便在后续的遍历中快速查找到与当前数字配对的另一个数字。
让我们一步步来解释这个解法:
- 我们首先创建一个空的哈希表
map,用来存储数组中的元素和它们的索引。在 JavaScript 中,可以使用Map数据结构来实现哈希表。
const map = new Map();
- 接下来,我们进行一次遍历数组
nums,在每次遍历中,计算当前元素与目标值target的差值complement。我们需要找到另一个数字与当前数字的和为目标值target。
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
}
- 对于当前遍历的数字
nums[i],我们检查哈希表中是否存在键为complement的项,即是否存在另一个数字与当前数字的和为目标值target。
if (map.has(complement)) {
// 找到目标数字对应的另一个数字,返回它们的索引
return [map.get(complement), i];
}
- 如果哈希表中不存在
complement这个键,则将当前数字及其索引存入哈希表中。
map.set(nums[i], i);
- 如果遍历完成后仍然没有找到符合条件的数字对,则返回一个空数组,表示未找到满足条件的数字对。
return [];
完整解题代码:
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (map.has(complement)) {
return [map.get(complement), i];
}
map.set(nums[i], i);
}
return [];
}
通过这种方法,我们只需要一次遍历数组,就可以在平均情况下在常量时间内完成查找操作,因此时间复杂度为 O(n),其中 n 是数组
nums 的长度。这个解法避免了暴力搜索的高时间复杂度,使得解题更加高效。
双指针解决方法
function twoSum(nums, target) {
// 克隆并排序数组
const sortedNums = [...nums].sort((a, b) => a - b);
// 初始化双指针
let left = 0;
let right = sortedNums.length - 1;
// 双指针遍历
while (left < right) {
const sum = sortedNums[left] + sortedNums[right];
if (sum === target) {
// 找到目标数字对应的两个数字
// 在原数组中查找这两个数字的下标
const index1 = nums.indexOf(sortedNums[left]);
let index2 = nums.indexOf(sortedNums[right]);
// 如果两个数字相同,则从 index2 后面查找第二个数字的下标
if (sortedNums[left] === sortedNums[right]) {
index2 = nums.indexOf(sortedNums[right], index1 + 1);
}
return [index1, index2];
} else if (sum < target) {
left++;
} else {
right--;
}
}
// 遍历完成,未找到符合条件的数字对
return [];
}
- **排序数组:**首先,我们将给定的整数数组
nums进行排序。这是因为双指针方法通常要求数组是有序的,这样才能更方便地进行双指针遍历。 - **初始化双指针:**我们初始化两个指针
left和right,分别指向数组的头部和尾部。在排序后的数组中,left指针指向的元素比right指针指向的元素小。 - **双指针遍历:**在每次遍历过程中,我们计算指针所指向的两个数的和,并与目标值进行比较。如果和等于目标值,则说明我们找到了符合条件的两个数,它们的下标分别为
left和right。如果和小于目标值,则将left指针向右移动一位,以增大和的值;如果和大于目标值,则将right指针向左移动一位,以减小和的值。重复这个过程,直到找到符合条件的数字对或者left指针超过了right指针,表示遍历完成。 - **返回结果:**如果我们找到了符合条件的两个数,则返回它们的下标数组;如果遍历完成后仍然没有找到符合条件的数字对,则返回一个空数组,表示未找到满足条件的数字对。
通过双指针方法,我们可以在一次遍历的过程中就找到符合条件的数字对,大大提高了算法的效率。这个方法的时间复杂度为 O(nlogn),其中 n 是数组 nums 的长度,因为我们需要对数组进行排序。然后,双指针的遍历过程只需要一次遍历,因此时间复杂度为 O(n)。
两种方法的优缺点分析
哈希表解法:
优点:
- 时间复杂度低: 在平均情况下,哈希表的查找操作可以在常量时间内完成,因此哈希表解法的时间复杂度为 O(n),其中 n 是数组的长度。
- 适用性广泛: 哈希表适用于不需要对数组进行排序的情况,而且可以适用于更广泛的情况,例如需要找到所有满足条件的数字对。
- 易于实现: 哈希表的实现相对简单,只需要遍历一次数组并在哈希表中记录数字及其索引即可。
缺点:
- 额外空间消耗: 哈希表需要额外的空间来存储数字及其索引,可能会占用较多的内存空间,尤其是当数组中的数字比较大时。
- 不保证顺序: 哈希表无法保证数字对的顺序,因此返回的结果可能是任意顺序的。
双指针解法:
优点:
- 空间复杂度低: 双指针解法不需要额外的空间来存储数字及其索引,因此空间复杂度为 O(1)。
- 不需要额外空间: 由于不需要额外的空间,因此可以在空间有限的情况下使用,例如在嵌入式系统或内存受限的环境中。
- 保证顺序: 双指针解法保证返回的数字对是按照数组的顺序排列的。
缺点:
- 需要排序: 双指针解法需要对数组进行排序,时间复杂度为 O(nlogn),其中 n 是数组的长度。
- 不适用于所有情况: 双指针解法通常适用于已排序的数组或特定情况下的数组,例如需要找到满足特定条件的两个数的情况。如果数组无法排序或需要找到所有满足条件的数字对,则双指针解法可能不适用。
综上所述,哈希表解法适用于大多数情况下,尤其是需要找到所有满足条件的数字对时;而双指针解法适用于已排序的数组或需要保证返回顺序的情况下,且具有较低的空间复杂度。在实际应用中,可以根据具体问题的特点和需求选择合适的解法。
结语
无论选择哈希表解法还是双指针解法,刷题的过程本身就是一次提升算法能力的过程。通过不断地思考、实践和总结,我们能够逐渐掌握更多的解题技巧,提升解决问题的能力。同时,刷题也是一种锻炼思维和逻辑能力的过程,能够帮助我们更好地理解和应用算法和数据结构的知识。
在刷题的过程中,我们不仅可以学习到解决特定问题的方法,还能够培养良好的学习习惯和解决问题的思维方式。通过与他人交流、分享和讨论,我们能够加深对算法和数据结构的理解,提高编程水平,成为一名优秀的程序员。
因此,刷题不仅是为了应付面试或者提升职业发展,更是一种提升个人能力、追求卓越的过程。希望每位刷题者都能够坚持不懈,不断进步,不断挑战自我,在编程的路上越走越远! 诸君,共勉。