LeetCode刷题001——两数之和(Easy)

001.两数之和

难度:简单

题目

给定一个整数数组 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
  • 只会存在一个有效答案

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

方法一:暴力枚举

思路及算法

最容易想到的方法是枚举数组中的每一个数 x,寻找数组中是否存在 target - x

当我们使用遍历整个数组的方式寻找 target - x 时,需要注意到每一个位于 x 之前的元素都已经和 x 匹配过,因此不需要再进行匹配。而每一个元素不能被使用两次,所以我们只需要在 x 后面的元素中寻找 target - x

代码

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)

方法二:暴力枚举改进

思路及算法

进行了一点改进,对内层 for 进行修改
int j = 0 ; j < nums.length ; j++ 改成 int j = nums.length - 1 ; j > i ; j --
前者 j0到数组长度循环;
后者从数组长度i 循环;减少循环次数;

代码

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 = nums.length-1 ; j > i ;j--){
                if(nums[i]+nums[j]==target){
                    if(i!=j){
                        res[0]=i;
                        res[1]=j;
                    }
                }
            }
        }
        return res;
    }
}

在这里插入图片描述

用时从97ms --> 49ms用时减半
内存消耗从42.57MB --> 42.08MB ,几乎没有变化;

复杂度分析

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

方法三:哈希表

思路及算法

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

使用哈希表,可以将寻找 target - x 的时间复杂度降低到从 O(N) 降低到 O(1)

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

代码

class Solution {
    public int[] twoSum(int[] nums, int target) {
    	//创建一个 HashMap 对象 hashtable, 整型(Integer)的 key 和整型(Integer)的 value
        Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
        for (int i = 0; i < nums.length; ++i) {
        	//hashtable.containsKey(hashtable.containsKey具体使用方法见附3) 查找 key = target - nums[i] 在哈希表中是否存在,存在则从哈希表中取出( target - nums[i] , i ),不存在则将(nums[i], 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 是数组中的元素数量。主要为哈希表的开销

方法四:哈希表改进

思路及算法

在哈希表的基础上仍可改进,即每次在哈希表中搜索补数时,设置left和right两个指针,一次搜索nums中左右两端的元素的补数,可以得到耗时0ms的效果。

代码

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)

方法五:排序后折半查找

思路及算法

先将元素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:力扣001-两数之和(题目及解答参考)
2:Java.util.HashMap 类讲解
3:java.util.HashMap.containsKey() 方法详解
4:java.util.HashMap.get() 方法详解
5:java.util.HashMap.put() 方法详解
6:Java.util.Arrays.sort()详解
7:Java.util.Arrays.copyOf()详解
8:解题方法详解参考文章
9:方法二画解

另附集合框架体系图:
在这里插入图片描述

  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值