Leetcode第1题: 两数之和的三种解法

一、题目来源:leetcode第一题

二、题目描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 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 <= 10(4)
  • -10(9) <= nums[i] <= 10(9)
  • -10(9) <= target <= 10(9)
  • 只会存在一个有效答案

进阶: 你可以想出一个时间复杂度小于 O(n^2) 的算法吗?

三、解题

3.1 解法一

  1. 思路

两层for循环遍历,直到找到和为目标值 target 的那两个整数,并返回它们的数组下标。

  1. 解法
const twoSum = function ( nums, target ) {
    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];
            } 
        }
    }
}; 
  1. 时间复杂度

该方法使用了两层嵌套的循环。外层循环从0到nums.length-1遍历数组中的每个元素,内层循环从外层循环的下一个元素开始遍历到数组的最后一个元素。因此,外层循环执行nums.length次,内层循环执行(nums.length-1) + (nums.length-2) + … + 1次。在最坏情况下,内层循环的总执行次数约为(nums.length2)/2。因此,该方法的时间复杂度为**O(n2)** ,其中n是数组 nums 的长度。

  1. 空间复杂度

该方法只使用了常数级别的额外空间,即只创建了一个常数大小的数组来存储结果。因此,该方法的空间复杂度为O(1)

3.2 解法二

  1. 思路

我们可以先对 nums 排序,然后利用左右双指针,从两端相向而行找到和为目标值 target 的那两个整数,并返回在原始数组中它们的数组下标。

  1. 解法
const twoSum = function ( nums, target ) {  
    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];
            }
        } 
    } 
}; 
  1. 时间复杂度
  • 复制数组:oringinalNums = [...nums]这一步需要遍历整个数组,因此时间复杂度为O(n),其中n为数组nums的长度。
  • 排序数组:nums.sort((a, b) => a - b)这一步使用快速排序算法,其平均时间复杂度为O(nlogn)。
  • 双指针移动:在while循环中,左指针和右指针进行移动,最多移动n次(即数组的长度),因此时间复杂度为O(n)。
  • 综上所述,该方法的时间复杂度为O(nlogn)
  1. 空间复杂度

在算法中创建了一个新的数组oringinalNums来保存原始的nums,其长度与nums相同,因此需要O(n)的额外空间。其他变量的空间使用是常数级别的,不随输入规模变化,因此可以忽略。因此,总体空间复杂度为O(n)

3.3 解法三

  1. 思路

对于一个元素 nums[i],你想知道有没有另一个元素 nums[j] 的值为 target - nums[i],可以用一个哈希表记录每个元素的值到索引的映射,这样就能快速判断数组中是否有一个值为 target - nums[i] 的元素了。

  1. 解法
const twoSum = function ( nums, target ) {
    // 维护 val -> index 的映射
    const valToIndex = new  Map ();
    for ( let i = 0 ; i < nums. length ; i++) {
        // 查表,看看是否有能和 nums[i] 凑出 target 的元素
        const need = target - nums[i];
        if (valToIndex. has (need)) {
            return [valToIndex. get (need), i];
        }
        // 存入 val -> index 的映射
        valToIndex. set (nums[i], i);
    }
    return  null ;
};
  1. 时间复杂度
  • 创建映射表:在for循环之前,通过const valToIndex = new Map()创建了一个空的Map对象。这一步的时间复杂度是O(1)。
  • 遍历数组并查找映射表:在for循环中,遍历了整个数组,并且对于每个元素,通过valToIndex.has(need)进行查找,其中need是目标值与当前元素的差。Map查找的时间复杂度是O(1)。因此,整个遍历过程的时间复杂度是O(n),其中n为数组nums的长度。
  • 综上所述,该方法的时间复杂度为O(n)
  1. 空间复杂度

该方法使用了一个映射表valToIndex来存储元素值和索引的映射关系。在最坏的情况下,映射表需要存储整个数组的所有元素,因此空间复杂度为O(n) ,其中n为数组nums的长度。

四、知识点

常见时间复杂度,按照增长速率从小到大进行排序:
  1. O(1):常数时间复杂度,表示算法的执行时间是一个常数,不随输入规模的增加而改变。例如,访问数组中的某个元素、进行固定次数的循环等。
  2. O(log n):对数时间复杂度,表示算法的执行时间与输入规模的对数成正比。常见的具有对数时间复杂度的算法有二分查找、平衡二叉树的插入和删除操作等。
  3. O(sqrt(n)):平方根时间复杂度,表示算法的执行时间与输入规模的平方根成正比。例如,求解素数的方法中的试除法。
  4. O(n):线性时间复杂度,表示算法的执行时间与输入规模成线性关系。例如,遍历数组、查找最大值或最小值等。
  5. O(n log n):线性对数时间复杂度,表示算法的执行时间与输入规模的乘积与对数的乘积成正比。常见的具有线性对数时间复杂度的算法有快速排序、归并排序等。
  6. O(n^2):平方时间复杂度,表示算法的执行时间与输入规模的平方成正比。例如,嵌套循环遍历二维数组、冒泡排序等。
  7. O(2^n):指数时间复杂度,表示算法的执行时间随着输入规模的增加呈指数级增长。例如,求解斐波那契数列的递归算法。
  8. O(n!):阶乘时间复杂度,表示算法的执行时间随着输入规模的增加呈阶乘级增长。例如,求解旅行商问题的穷举算法。
  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值