每日一道算法:两数之和

简介: 每天一道算法题,可以让自己的思维和深度慢慢无限延伸。

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

说明:你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:

给定 nums = [2, 7, 11, 15],target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

每日一道算法:两数之和

解法一:暴力法

思路分析:

暴力法很简单,遍历每个元素 x,并查找是否存在一个值与 target−x 相等的目标元素

PHP代码实现:

/**
 * @param Integer[] $nums
 * @param Integer $target
 * @return Integer[]
 */
function twoSum($nums, $target) {
    for($i=0;$i<count($nums);$i++){
        for($j=$i+1;$j<count($nums);$j++){
            if($nums[$i] + $nums[$j] == $target){
                return [$i,$j];
            }
        }
    }
}

复杂度分析:

时间复杂度:O(n^2)
对于每个元素,我们试图通过遍历数组的其余部分来寻找它所对应的目标元素,这将耗费 O(n) 的时间
空间复杂度:O(1)

解法二:两遍哈希表

思路分析:

为了对运行时间复杂度进行优化,我们需要一种更有效的方法来检查数组中是否存在目标元素。如果存在,我们需要找出它的索引。保持数组中的每个元素与其索引相互对应的最好方法是什么?哈希表(hashTable)。

通过以空间换取速度的方式,我们可以将查找时间从 O(n) 降低到 O(1)。哈希表正是为此目的而构建的,它支持以 近似 恒定的时间进行快速查找。我用“近似”来描述,是因为一旦出现冲突,查找用时可能会退化到 O(n)。但只要你仔细地挑选哈希函数,在哈希表中进行查找的用时应当被摊销为 O(1)。

一个简单的实现使用了两次迭代。在第一次迭代中,我们将每个元素的值和它的索引添加到表中。然后,在第二次迭代中,我们将检查每个元素所对应的目标元素(target - nums[i])是否存在于表中。注意,该目标元素不能是 nums[i] 本身!

采用数组函数 `array_keys()` 来解题, 返回包含数组中所有键名的一个新数组
`array_keys()` 是一个哈希函数

PHP代码实现:

/**
 * @param Integer[] $nums
 * @param Integer $target
 * @return Integer[]
 */
function twoSum($nums, $target) {
    for($i=0;$i<count($nums);$i++){
        $difNum = $target - $nums[$i];
        $keys = array_keys($nums,$difNum);
        foreach($keys as $key){
            if($key && $key != $i){
                return [$i,$key];
            }
        }
    }
}

复杂度分析:

时间复杂度:O(n)
我们把包含有 n 个元素的列表遍历两次。由于哈希表将查找时间缩短到 O(1) ,所以时间复杂度为 O(n)
空间复杂度:O(n)
所需的额外空间取决于哈希表中存储的元素数量,该表中存储了 n个元素

解法三:一遍哈希表

思路1:array_keys_exists()

思路分析:

事实证明,我们可以一次完成。在进行迭代并将元素插入到表中的同时,我们还会回过头来检查表中是否已经存在当前元素所对应的目标元素。如果它存在,那我们已经找到了对应解,并立即将其返回。

采用数组函数 `array_key_exists()` 来解题, 判断数组是否存在此键名
`array_key_exists()` 是一个哈希函数

PHP代码实现:

/**
 * @param Integer[] $nums
 * @param Integer $target
 * @return Integer[]
 */
function twoSum($nums, $target) {
    $find = [];
    for($i=0;$i<count($nums);$i++){
        $difNum = $target - $nums[$i];
        if(array_key_exists($difNum,$find)){
            return [$find[$difNum],$i];
        }
        $find[$nums[$i]] = $i;
    }
}

复杂度分析:

时间复杂度:O(n)
我们只遍历了包含有 n个元素的列表一次。在表中进行的每次查找只花费 O(1)的时间
空间复杂度:O(n)
所需的额外空间取决于哈希表中存储的元素数量,该表最多需要存储 n个元素

思路2:使用isset()代替array_key_exists()

思路分析:

`isset()`性能比`array_key_exists()`要好,因为`isset()`是语言结构,而`array_key_exists()`是函数,语言结构的解析运行要比函数快一点。

PHP代码实现:

/**
 * @param Integer[] $nums
 * @param Integer $target
 * @return Integer[]
 */
function twoSum($nums, $target) {
    $find = [];
    for($i=0;$i<count($nums);$i++){
        $difNum = $target - $nums[$i];
        if(isset($find[$difNum])){
            return [$find[$difNum],$i];
        }
        $find[$nums[$i]] = $i;
    }
}

复杂度分析:

时间复杂度:O(n)
空间复杂度:O(n)
小贴士:
* isset()效率高于array_key_exists(), PHP7之后有30%左右的提升, php5.6有将近70%的提升
* isset()是语法结构, array_key_exists()是函数, 调用开销要小
* isset()通过 Z_TYPE_P 获取变量类型,然后再进行判断实现的; array_key_exists()则是通过hash查找来实现的
* 对于数组,isset()的性能要高于array_key_exists() 所以,如果数组比较大,我们应该用如下方法保证性能和准确性 isset()

解法四:二分查找法

思路分析:

用一个排序都能把复杂度降到 O(nlogn),通过排序,然后用两个指针从前后扫描逼近真值。

注意这个思想,可以让 O(n^2) 的复杂度降为 O(n),充分利用排序,因为一定会有一个值满足,然后通过值去原数组里找对应的下标。

前提,该数组已经是一个有序数组,必须先排序,再查找

PHP代码实现:

/**
 * @param Integer[] $nums
 * @param Integer $target
 * @return Integer[]
 */
function twoSum($nums, $target) {
    $origin = $nums;
    sort($nums);
    $left = 0;
    $right = count($nums) - 1;
    while($left <= $right){
        if($nums[$left] + $nums[$right] == $target){
            $start = array_keys($origin,$nums[$left]);
            $end = array_keys($origin,$nums[$right]);
            return [reset($start),end($end)];
        }elseif($nums[$left] + $nums[$right] > $target){
            $right -= 1;
        }elseif($nums[$left] + $nums[$right] < $target){
            $left += 1;
        }
    }
}

复杂度分析:

时间复杂度:O(logn)
空间复杂度:O(1)

四种解法的性能对比

每日一道算法:两数之和

时间复杂度:

Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<O(n^2)<O(n^3)<…<Ο(2n)<Ο(n!)

结论:

一般算法是否最优,主要看时间复杂度,往往最优的算法需要牺牲空间复杂度
一遍哈希表 < 二分查找法 < 两遍哈希表 < 暴力法

github

LeetCode_PHP:https://github.com/zhangdejian/LeetCode_PHP

后记

自己只能解出第一和第二种思路,原来还有其它思路解,soga,涨知识了。

自己不属于那种最聪明的或最有天赋的人,只能后天靠努力来补啦哈,反正多刷刷算法题,准没错,可以让自己的思维和深度无限延伸。

题目来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/two-sum

目录
相关文章
|
6月前
|
算法
算法每日一题---两数之和
算法每日一题---两数之和
30 0
|
6月前
|
存储 算法 Python
python 算法 两数之和 的多种解法
python 算法 两数之和 的多种解法
|
缓存 算法 Java
【手绘算法】力扣 1 两数之和(Two Sum)
Hi,大家好,我是Tom。一个美术生转到Java开发的程序员。从今天开始,我将带大家每天刷一道题。我会用手绘的方式给大家讲解解题思路。在解题过程中,也会手写一些伪代码。当然,如果想要完整的源码的话,可以到我的个人主页简介中获取。 今天给大家分享的是力扣启蒙题第1题,求两数之和。虽然很简单,但是它的通过率只有52%。
92 0
|
3月前
|
存储 算法 Java
LeetCode初级算法题:两数之和+斐波拉契数列多种java解法
LeetCode初级算法题:两数之和+斐波拉契数列多种java解法
36 0
|
5月前
|
存储 算法 Java
【经典算法】LeetCode1:两数之和(Java/C/Python3实现含注释说明,Easy)
【经典算法】LeetCode1:两数之和(Java/C/Python3实现含注释说明,Easy)
30 1
|
6月前
|
存储 算法
Leetcode 30天高效刷数据结构和算法 Day1 两数之和 —— 无序数组
给定一个无序整数数组和目标值,找出数组中和为目标值的两个数的下标。要求不重复且可按任意顺序返回。示例:输入nums = [2,7,11,15], target = 9,输出[0,1]。暴力解法时间复杂度O(n²),优化解法利用哈希表实现,时间复杂度O(n)。
49 0
|
6月前
|
存储 算法
【优选算法专栏】专题九:链表--------两数之和
【优选算法专栏】专题九:链表--------两数之和
33 0
|
6月前
|
算法 C++
双指针算法·两数之和·三数之和
双指针算法·两数之和·三数之和
48 0
|
6月前
|
算法 C++
【牛客-算法】NC61 两数之和(哈希表的运用,C++)
前言 🔥 该专栏作为算法题笔记,记录算法的思路、遇到的问题,以及能跑的代码,持续更新中! 🔥 推荐一款面试、刷题神器牛客网:👉开始刷题学习👈
235 1
|
6月前
|
算法 Java
LeetCode算法题---两数之和(一)
LeetCode算法题---两数之和(一)
54 0