leetcode-1-两数之和

两数之和

题目描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

ini 复制代码
输入: nums = [2,7,11,15], target = 9
输出: [0,1]
解释: 因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

ini 复制代码
输入: nums = [3,2,4], target = 6
输出: [1,2]

示例 3:

ini 复制代码
输入: nums = [3,3], target = 6
输出: [0,1]

提示:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案

暴力解法

  1. 思路

    • 使用两层嵌套循环遍历数组。外层循环从数组的第一个元素开始,内层循环从外层循环当前元素的下一个元素开始。对于每一对元素,计算它们的和,并检查是否等于目标值 target。如果相等,则返回这两个元素的下标。
  2. 代码实现

ts 复制代码
function twoSum(nums: number[], target: number): number[] {
    for (let i = 0; i < nums.length; i++) {
        for (let j = i + 1; j < nums.length; j++) {
            if (nums[i] + nums[j] === target) {
                return [i, j];
            }
        }
    }
    return [];
}
  1. 分析
  • 时间复杂度 :O(n2),因为有两层嵌套循环,外层循环执行 n 次,内层循环对于每次外层循环平均执行 (n - 1) / 2 次,总体时间复杂度为 O(n×n)。
  • 空间复杂度:O(1),除了存储结果的数组外,只使用了常数级别的额外空间。
  1. 缺点:时间复杂度较高,当数组长度较大时,运行效率很低。对于大规模数据,计算量会急剧增加。

哈希表解法

  1. 思路
  • 使用一个哈希表(在 TypeScript 中可以用对象或者Map模拟)来存储数组元素及其下标。遍历数组时,对于每个元素 nums[i],计算 target - nums[i],然后检查这个差值是否已经在哈希表中。如果在哈希表中,则说明找到了两个数的和为 target,返回哈希表中差值对应的下标和当前下标 i。如果不在哈希表中,则将当前元素 nums[i] 及其下标 i 存入哈希表,继续遍历。
  1. 代码实现
ts 复制代码
//对象
function twoSum(nums: number[], target: number): number[] {
    const hashMap: { [key: number]: number } = {};
    for (let i = 0; i < nums.length; i++) {
        const complement = target - nums[i];
        if (complement in hashMap) {
            return [hashMap[complement], i]
        }
        hashMap[nums[i]] = i;
    }
    return [];
}

// Map
function twoSum(nums: number[], target: number): number[] {
  const hashMap = new Map();
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    if (hashMap.has(complement)) {
      return [hashMap.get(complement), i];
    }
    hashMap.set(nums[i], i);
  }
  return [];
}
  1. 分析

    • 时间复杂度:O(n),因为只需要遍历数组一次。每次在哈希表中查找和插入操作平均时间复杂度为 O(1)。
    • 空间复杂度:O(n),在最坏情况下,哈希表需要存储数组中的所有元素。
  2. 优点:相比暴力解法,哈希表解法的时间复杂度大大降低,在处理大规模数据时效率更高。虽然空间复杂度为 O(n),但在实际应用中,时间效率的提升往往更为关键。

最优解及原因

  1. 最优解:哈希表解法是本题的最优解。
  2. 原因:在大多数情况下,尤其是处理大规模数据时,时间复杂度是衡量算法优劣的重要指标。哈希表解法将时间复杂度从暴力解法的 O(n2) 降低到了 O(n),这使得算法在运行时间上有了显著的提升。虽然哈希表解法需要额外的 O(n) 空间,但以空间换取时间的方式在很多场景下是值得的。

拓展和题目变形

1.拓展

  • 如果需要返回所有满足和为 target 的二元组,而不只是一组解。

  • 思路:可以使用一个数组来存储所有符合条件的二元组。在哈希表解法的基础上,每次找到符合条件的二元组时,将其添加到结果数组中。

  • 代码实现

ts 复制代码
function twoSumAllPairs(nums: number[], target: number): number[][] {
    const hashMap: { [key: number]: number } = {};
    const result: number[][] = [];
    for (let i = 0; i < nums.length; i++) {
        const complement = target - nums[i];
        if (complement in hashMap) {
            result.push([hashMap[complement], i]);
        }
        hashMap[nums[i]] = i;
    }
    return result;
}
  1. 题目变形
  • 变形一 :如果数组中可能存在重复元素,且要求返回所有满足和为 target 的二元组,包括重复元素组成的二元组。

  • 思路:在上述返回所有二元组的代码基础上,修改哈希表的存储方式,使其可以处理重复元素。可以将哈希表的值改为数组,存储所有相同元素的下标。

  • 代码实现

ts 复制代码
function twoSumAllPairsWithDuplicates(nums: number[], target: number): number[][] {
    const hashMap: { [key: number]: number[] } = {};
    const result: number[][] = [];
    for (let i = 0; i < nums.length; i++) {
        const complement = target - nums[i];
        if (complement in hashMap) {
            for (const index of hashMap[complement]) {
                result.push([index, i]);
            }
        }
        if (!hashMap[nums[i]]) {
            hashMap[nums[i]] = [];
        }
        hashMap[nums[i]].push(i);
    }
    return result;
}
  • 变形二 :给定一个有序数组 nums,找到两个数使得它们的和最接近目标值 target

  • 思路:使用双指针法。初始化两个指针,一个指向数组开头,一个指向数组末尾。计算两个指针所指元素的和,与目标值比较。如果和等于目标值,直接返回;如果和大于目标值,将右指针左移;如果和小于目标值,将左指针右移。每次移动指针后,更新最接近目标值的和及对应的两个数。

  • 代码实现

ts 复制代码
function twoSumClosest(nums: number[], target: number): number[] {
    let left = 0;
    let right = nums.length - 1;
    let closestSum = nums[left] + nums[right];
    let result: number[] = [nums[left], nums[right]];
    while (left < right) {
        const sum = nums[left] + nums[right];
        if (sum === target) {
            return [nums[left], nums[right]];
        } else if (sum > target) {
            right--;
        } else {
            left++;
        }
        if (Math.abs(sum - target) < Math.abs(closestSum - target)) {
            closestSum = sum;
            result = [nums[left], nums[right]];
        }
    }
    return result;
}
相关推荐
JuneXcy2 小时前
循环高级(1)
c语言·开发语言·算法
Ka1Yan3 小时前
什么是策略模式?策略模式能带来什么?——策略模式深度解析:从概念本质到Java实战的全维度指南
java·开发语言·数据结构·算法·面试·bash·策略模式
绝无仅有3 小时前
Go Timer 面试指南:常见问题及答案解析
后端·算法·架构
地平线开发者4 小时前
开发者说|H-RDT:基于人类操作数据的跨本体机器人学习
算法·自动驾驶
biuyyyxxx6 小时前
Excel数组学习笔记
笔记·学习·算法
南莺莺6 小时前
//Q是一个队列,S是一个空栈,实现将队列中的元素逆置的算法。
数据结构·算法·链表·
闻缺陷则喜何志丹6 小时前
【分治法 BFS 质因数分解】P12255 [蓝桥杯 2024 国 Java B] 园丁|普及+
c++·算法·蓝桥杯·宽度优先·质因数分解·分治法
寒冬没有雪6 小时前
按对角线进行矩阵排序
c++·算法
Huangichin6 小时前
C++基础算法——贪心算法
c++·算法·贪心算法
sssvangen7 小时前
宝石组合(蓝桥杯)
算法·蓝桥杯·调和计数法