【LeetCode-01】两数之和

目录

前言

题目描述

解题方法

方法一:暴力求解法

思路以及实现

复杂度分析

方法二:暴力枚举改进

思路及其实现

复杂度分析

方法三:使用哈希表

思路及其实现

复杂度分析

方法四:哈希表改进

思路及其实现

复杂度分析

方法五:排序后折半查找

思路及其实现

复杂度分析

自己做

参考资料:


前言

声明:本文仅为学习记录,图片以及题目资源来自力扣(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 <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案

解题方法

方法一:暴力求解法

思路以及实现

循环遍历两遍数组两次分别取出两个数字进行相加,直到取出的两个数字之和为目标值。

注意:两个数字不能相同

所以代码如下:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] res=new int[2];
        for(int i = 0 ; i < nums.length ; i++){
            for(int j = 0 ; j < nums.length ;j++){
                if(nums[i]+nums[j]==target){
                    if(i!=j){
                        res[0]=i;
                        res[1]=j;
                    }
                }
            }
        }
        return res;
    }
}

复杂度分析

时间复杂度:O(N²),其中 N 是数组中的元素数量。最坏情况下数组中任意两个数都要被匹配一次

空间复杂度:O(1)

方法二:暴力枚举改进

思路及其实现

注意到每次遍历时比如现有数组[1,2,3,4,5] 实际上1 + 2与2 + 1 是一样的意思且又由于不能使用相同的数字,实际上第二次遍历就只需要遍历第一次遍历的数字之后的即可,即1+2,1+3,1+4,1+5;2+3,2+4;2+5等等。代码表现为for(int j = i + 1;j<n;j++)

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length;
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                if (nums[i] + nums[j] == target) {
                    return new int[]{i, j};
                }
            }
        }
        return new int[0];
    }
}。

复杂度分析

  • 时间复杂度:O(N²)

对于每个元素 nums[i],它只与 i+1 之后的元素进行比较,这样就避免了与自己和之前元素的比较,减少了比较次数。因此,每个元素只被比较 n-1 次,但是每次比较都是与不同的元素进行的,所以总的比较次数是 n*(n-1)/2,即 O(n^2)。但是,由于每次比较都是与不同的元素进行,所以实际上这个算法的时间复杂度仍然是 O(n^2),但是常数因子较小,因此执行速度会比之前的代码快。

  • 空间复杂度:O(1)

代码中没有使用额外的数据结构来存储中间结果,只是返回了一个包含两个索引的数组,这个数组的大小与输入数组 nums 的大小无关。因此,空间复杂度是 O(1),即常数级别的空间复杂度。

方法三:使用哈希表

思路及其实现

注意到方法一的时间复杂度较高的原因是寻找 target - x 的时间复杂度过高。因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。

问题转化为目标值与数组中的数值相减存入哈希表,若哈希表中有相同的值,证明他们两个相加等于这个数。使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N) 降低到 O(1)。

这样我们创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配。

代码

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums.length; ++i) {
            if (hashtable.containsKey(target - nums[i])) {
                return new int[]{hashtable.get(target - nums[i]), i};
            }
            hashtable.put(nums[i], i);
        }
        return new int[0];
    }
}

复杂度分析

时间复杂度:O(N),其中 N 是数组中的元素数量。对于每一个元素 x,我们可以 O(1) 地寻找 target - x。

空间复杂度:O(N),其中 N 是数组中的元素数量。主要为哈希表的开销。

方法四:哈希表改进

思路及其实现

算法意图:

  1. 边界条件检查:首先检查输入数组 nums 是否为空或者长度小于2,如果是,则返回一个空数组。
  2. 初始化哈希表:创建一个 HashMap 来存储遍历过程中遇到的数字及其索引。
  3. 双指针法:使用两个指针 leftright 分别指向数组的起始和结束位置。
  4. 循环遍历:在 while 循环中,同时从数组的两端向中间遍历,尝试找到两个数的和等于目标值 target
  5. 查找补数:对于左指针指向的值 ln0,计算它的补数 ln1(即 target - ln0),并检查这个补数是否已经在哈希表中。如果在,说明找到了两个数的和等于 target,并返回它们的索引。对于右指针指向的值 rn0,也做同样的操作。
  6. 更新哈希表:如果左指针的补数不在哈希表中,则将左指针的值和索引存入哈希表,并将左指针向右移动。如果右指针的补数不在哈希表中,则将右指针的值和索引存入哈希表,并将右指针向左移动。
  7. 返回结果:如果循环结束都没有找到符合条件的两个数,则返回一个空数组。
public int[] twoSum(int[] nums, int target) {
    if(nums == null || nums.length < 2) {
        return new int[] {};
    }
    Map<Integer, Integer> map = new HashMap<>();
    int left = 0;//左指针,下标为0
    int right = nums.length - 1;//右指针,下标为nums长度-1
    while(left <= right) {
        int ln0 = nums[left];//左值
        int ln1 = target - ln0;//左值补数
        int rn0 = nums[right];//右值
        int rn1 = target - rn0;//右值补数
        if(map.containsKey(ln1)) {//寻找哈希表中有无左值补数
            return new int[] {left, map.get(ln1)};//有则返回左值补数(左下标,左值补数对应的下标)
        }
        else {
            map.put(ln0, left++);//无则写入(左值,左下标)写入后左下标+1
        }
        if(map.containsKey(rn1)) {//寻找哈希表中有无右值补数
            return new int[] {right, map.get(rn1)};//有则返回右值补数(右下标,右值补数对应的下标)
        }
        else {
            map.put(rn0, right--);//无则写入(右值,右下标)写入后右下标-1
        }
    }
    return new int[] {};
}

复杂度分析

  • 时间复杂度取决于遍历原数组耗时,为O(n)
  • 使用了哈希表存储数组内容,空间复杂度O(n)

运行时间能达到0ms

方法五:排序后折半查找

思路及其实现

先将元素nums拷贝一份为copiedNums,

然后将原数组排序,

接着遍历排序后的数组,

以遍历到的数字之后的数列执行折半查找,

查找目标为target - nums[i]。

因为原数组排序后丢失原下标信息,

因此执行折半查找得到n0和n1(n0 + n1 = target)后,

再遍历两次copiedNums分别得到n0和n1在copiedNums中的下标(原下标)。

排序和折半查找可以调用Arrays静态方法Arrays.sort()和Arrays.binarySearch(),也可以自己实现。

代码示例中排序用Arrays.sort(),折半查找用自己实现的版本。

public int[] twoSumSortBinarySearch(int[] nums, int target) {
    int[] res = new int[2];
    int[] resVal = new int[2];
    int[] copiedNums = Arrays.copyOf(nums, nums.length);//拷贝nums的元素放在copiedNums.
    Arrays.sort(nums);//对nums进行排序
    int n1 = -1;
    for (int i = 0; i < nums.length; i++) {
        resVal[0] = nums[i];
        resVal[1] = target - resVal[0];
        // 也可以用Arrays自带的折半查找方法Arrays.binarySearch(),
        // 但要注意判断返回值的地方要做相应修改。
        // n1 = Arrays.binarySearch(nums, i + 1, nums.length -1 , resVal[1]);
        n1 = binarySearchBasic(nums, i + 1, resVal[1]);
        if(n1 != -1) {
            break;
        }
    }
    if(n1 == -1) {
        return new int[] {};
    }
    for (int j = 0; j < copiedNums.length; j++) {
        if(copiedNums[j] == resVal[0]) {
            res[0] = j;
            break;
        }
    }
    for (int k = 0; k < copiedNums.length; k++) {
        // 注意不能是同一个元素,需加上 k != res[0] 条件
        if(copiedNums[k] == resVal[1] && k != res[0]) {
            res[1] = k;
            break;
        }
    }
    return res;
}

private int binarySearchBasic(int[] arr, int low, int target) {
    int high = arr.length - 1;
    while(low <= high) {
        int center = (low + high) / 2;
        if(target == arr[center]) {
            return center;
        }
        else if(target < arr[center]) {
            high = center - 1;
        }
        else {
            low = center + 1;
        }
    }
    return -1;
}

复杂度分析

时间复杂度:排序耗时O(nlogn)(假设采用O(nlogn)的排序算法),拷贝原数组耗时O(n),排序后的折半查找耗时为O(logn),最后找原下标的两个for均耗时O(n),所以总体时间复杂度为O(nlogn)。

空间复杂度:O(n)

自己做

  1. 自己的思路
  2. 答案思路
  3. 自己为什么没想到
  4. 数据结构知识点
  5. Java语句

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

参考资料:

1:1. 两数之和 - 力扣(LeetCode)(题目及解答参考)

2:解题方法详解参考文章

3:LeetCode刷题001——两数之和(Easy)_leetcode两数之和-优快云博客

4:Java数据结构之哈希表_java 哈希表-优快云博客

5:Java数据结构-哈希表的实现(hash)_java哈希表的实现-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值