在哈希表中查找一个key的时间复杂度到底是多少?--leetcode 1

在哈希表中查找一个key的时间复杂度到底是多少?–leetcode 1

不出意外的话,这应该是我的第一篇博客。

今天下午上课,听的东西完全不进脑子,状态奇差(不过好像很多课我都这样),于是打开几个月没碰的leetcode找找状态,顺便复习一下以前做过的题。没想到才做leetcode 1就发现事情不对劲。闲话少说,先上题。

给定一个整数数组nums和一个目标值target,请你在该数组中找出和为目标值的那两  
个整数,并返回他们的数组下标。你可以假设每个输入只对应一种答案,但是你不能
重复利用这个数组中同样的元素。
实例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回[0, 1]

题目的意思很清晰了,不做解释。而这道题在leetcode上难度是easy。看到这种题,只要当时不是没睡醒,基本上都是能两下子就想到解法的。既然这样,那就要想办法优化算法,尽量降低时间复杂度和空间复杂度,尤其是时间复杂度。而我一拿到这个题的时候,脑子里马上就冒出来暴力搜索。

解法1:暴力搜索

暴力搜索,外层循环遍历一遍数组,内层循环也遍历一遍数组。这样的时间复杂度是O(n^2)。显然,这是不能让人满意的。

解法2:排序后二分查找

将数组排序,然后一层循环遍历一遍数组,对于数组中的每个元素it,在数组中二分查找target - it。排序的时间复杂度是O(nlgn),二分查找n次的时间复杂度也是O(nlgn),因此整个算法的时间复杂度是O(n*lgn)。

算到这里,其实我心里是有种预感,还有更简单的算法的。以leetcode一贯的套路来看,数组的easy题,往往最优解法是不容易想到的,说不定有O(n)的解法。但是当时我状态的确不好,所以想了一会儿以后就作罢了,去海外官网的讨论区一查,果然有。

解法3:哈希表

我是看完了这个解法,再回来想一下这个解法是怎么想到的,尝试重现一下当时他们想到这个解法的心路历程。举例来说,现在有一个测试用例,target == 5,然后我们想要找到值为2的元素,有没有元素与其配对,也就是说,有没有值为3的元素。在上面两个解法中,暴力搜索的查找的时间复杂度是O(n),而排序后二分查找的时间复杂度是O(lgn)。而无论什么解法,遍历一遍数组是免不了的。所以如果想要把时间复杂度控制在O(n),那么对于每一个元素来说,想要判断它有没有配对的元素,并且定位这个配对元素的位置,花的时间不能超过O(1)。而搜索的时间复杂度为O(n)的数据结构有什么,没错,就是哈希表。

想明白这点,也就不难想到解法了。我们遍历一遍数组,对于每个元素it来说,我们去找哈希表中有没有target - it这个键,有的话,返回it的下标和哈希表中target - it的值,也就是它的下标;否则,把it作为键,it的下标作为值,存入哈希表中。最后在O(n)时间内总能找到两个和为target的数。

到这里,不知道有没有人会怀疑,这个算法的时间复杂度到底是不是O(n),因为在哈希表中,想找到一个key,时间复杂度真的是O(1)吗?没错,这就是这篇博客的重点,可以说,以下才是正文。我在看到这个解法的时候,小小的眼睛里闪烁着大大的疑惑:在哈希表中找key不也是要遍历的吗?为什么时间复杂度不是O(n)而是O(1)?于是我去请教舍友,并在他终于受不了,“你数据结构白学了”的时候去百度(其实翻去年的数据结构教材效果也是一样的),回想起了哈希映射这个概念。

哈希映射

什么是哈希映射?具体的概念不解释了(因为我也懒得翻书),意思就是说,假如现在有一个哈希表,有5个格子,编号分别为0,1,2,3,4,然后我们想把key == 11放进去,那么放到哪个格子呢?有一种方法是,11除以5的余数是1,那么就放到编号为1的格子里去,那么这个时候的哈希映射(映射函数)就是h(x) = x % 5。

有了映射函数,接下来看一下查找某个key的时间复杂度。假设我们要查找key == 11,我们会怎么做呢?遍历一遍哈希表?不不不,这也太low了,完全没有体现出哈希表的优越性。我们根据哈希映射h(x) = x % 5,把x == 11代进去,一下子就知道了在1号格子里,自然时间复杂度也就是O(1)。

至于哈希冲突,举个例子,在把key == 11放到1号格子之后,来了个key == 16,按照哈希映射应该放到1号格子,但是这里已经有key == 11了,怎么办?这个有机会接触到再好好说道说道吧,今天有点累,不说了。

AC代码的话,其实在说了这个解法之后,就没有太大意义了,放一个官网讨论区的吧。

class Solution
{
public:
vector twoSum(vector &numbers, int target)
    {
        //Key is the number and value is its index in the vector.
        unordered_map hash;
        vector result;
        for (int i = 0; i < numbers.size(); i++) 
        {
            int numberToFind = target - numbers[i];
            //if numberToFind is found in map, return them
            if (hash.find(numberToFind) != hash.end()) 
            {
                        //+1 because indices are NOT zero based
                result = { hash[numberToFind], i };   
                return result;
            }
            //number was not found. Put it in the map.
            hash[numbers[i]] = i;
        }
        return result;
    }
};

写到这里,大概就完事了吧。今天是这几个月第一次回去写写题,没想到就发现自己这么大一个知识盲区。之前我总说自己,对某些很底层很基础的东西是很欠缺的,很多人还不信,现在,你们信了吧。
小狗过河
2019.10.18

你可能感兴趣的:(leetcode)