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;
}
相关推荐
LeaderSheepH11 小时前
常见的排序算法
数据结构·算法·排序算法
周杰伦_Jay13 小时前
【图文详解】强化学习核心框架、数学基础、分类、应用场景
人工智能·科技·算法·机器学习·计算机视觉·分类·数据挖掘
violet-lz13 小时前
Linux静态库与共享库(动态库)全面详解:从创建到应用
算法
贝塔实验室13 小时前
ADMM 算法的基本概念
算法·数学建模·设计模式·矩阵·动态规划·软件构建·傅立叶分析
2351613 小时前
【LeetCode】3. 无重复字符的最长子串
java·后端·算法·leetcode·职场和发展
微笑尅乐14 小时前
神奇的位运算——力扣136.只出现一次的数字
java·算法·leetcode·职场和发展
自信的小螺丝钉14 小时前
Leetcode 155. 最小栈 辅助栈
leetcode·
吃着火锅x唱着歌14 小时前
LeetCode 3105.最长的严格递增或递减子数组
算法·leetcode·职场和发展
小卡皮巴拉14 小时前
【笔试强训】Day1
开发语言·数据结构·c++·算法
初圣魔门首席弟子14 小时前
switch缺少break出现bug
c++·算法·bug