游戏夜读 | Two Sum问题的八个解

文/良宵听雨。授权“游戏夜读”发表。

打开LeetCode找到一个小游戏

\1. Two Sum

Easy

Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

Example:

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

自助使用Python3款答题纸

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:

开始答题……其他略。题目中有两个假设:

1 有且仅有一个解;

2 同一个元素不能被使用两次。

尝试的5种方法,失败了4次

        # 方法1:循环遍历列表中的数,一个一个找
        # for i in nums:
        #     for j in nums:
        #         if nums.index(i) != nums.index(j):
        #             if (i+j)==target:
        #                 return [nums.index(i), nums.index(j)]
        # 上述方法1有个漏洞:当i=j,比如遇到[3,3],.index()的判断就不好用了

        
        # 方法2:基于方法1,循环遍历列表的索引,这样就避免数值相同的bug啦
        # for i in range(len(nums)):
        #     for j in range(len(nums)):
        #         if i != j:
        #             if nums[i] + nums[j] == target:
        #                 return [i, j]
        # 上述方法2虽然可行,但是Time Limit Exceeded,说明效率太低,被淘汰了
        
        
        # 方法3:根据题目中说的假设(恰有一个答案),直接匹配差值是否在列表中,并对相同值的情况做处理
        # for i in nums:
        #     if (target - i) in nums:
        #         if i == (target - i):
        #             return [nums.index(i), nums.index(i, nums.index(i)+1)]
        #         else:
        #             return [nums.index(i), nums.index(target-i)]
        # 上述方法3有漏洞:相同的值被取了两次实际却只有一个,遇到[3,2,4]就歇菜了,i=3没有两个3返回null
        
                
        # 方法4:基于方法3,加一个符合条件的数值数量的判断,弥补漏洞
        # for i in nums:
        #     if (target - i) in nums:
        #         if i == (target - i) and nums.count(i) > 1:
        #             return [nums.index(i), nums.index(i, nums.index(i)+1)]
        #         else:
        #             return [nums.index(i), nums.index(target-i)]
        # 上述方法4还是有漏洞,遇到[3,2,4]没有歇菜,但是返回了[0,0],判断条件没搞清楚逻辑,画NS图自救,
        # 发现是漏掉了i==(target-i) and .count(i)<1 这一类情况,应当continue,而不是直接进上述的else
        
        
        # 方法5:基于方法3和方法4,围绕不能复用的条件,进行两层判断:先挖坑,再网鱼,步骤有先后手
        for i in nums:
            if (target - i) in nums:
                if i == (target-i):
                    if nums.count(i) > 1:
                        return [nums.index(i), nums.index(i, nums.index(i)+1)]
                else:
                    return [nums.index(i), nums.index(target-i)]
        # 成功accepted了!
        # Runtime: 788 ms, faster than 35.47% of Python3 online submissions for Two Sum.
        # Memory Usage: 13.8 MB, less than 70.43% of Python3 online submissions for Two Sum.

上述方法小结

  1. 总体思路混乱,语无伦次,可悲。
  2. 冷静一下,算法的思路主要分两大类:
    2.1 直接查找计算法(找到一个i和一个j,判断相应和是否为target);
    2.2 间接查找问询法(找到一个i,再判断target与i的差值是否存在)。
  3. 再冷静一下,查找的思路主要分两大类:
    3.1 基于item
    3.2 基于index

综上,在已有的认知范围内,理论上有四个方法可以解决LeetCode这个“Two Sum”的问题,分别是:

(1) 基于item的直接查找计算法

        for i in nums:
            for j in nums:
                # find it!
                if (i+j)==target:
                    # maybe twice?
                    if i==j:
                        # more than 1 elements!
                        if nums.count(i)>1:
                            # don't forget find the 2nd one's index
                            return [nums.index(i), nums.index(i, nums.index(i)+1)]
                    # not twice
                    else:        
                        return [nums.index(i), nums.index(j)]

果然啊!成功!
Runtime: 4604 ms, faster than 25.25% of Python3 online submissions for Two Sum.
Memory Usage: 13.8 MB, less than 68.81% of Python3 online submissions for Two Sum.

(2) 基于index的直接查找计算法

        for i in range(len(nums)):
            for j in range(len(nums)):
                if (nums[i]+nums[j])==target:
                    if i==j:
                        if nums.count(nums[i])>1:
                            return [i, nums.index(nums[i], i+1)]
                    else:
                        return [i, j]

果然啊!成功!就是慢了点。
Runtime: 7688 ms, faster than 5.01% of Python3 online submissions for Two Sum.
Memory Usage: 13.8 MB, less than 73.03% of Python3 online submissions for Two Sum.

(3) 基于item的间接查找问询法

        for i in nums:
            if (target-i) in nums:
                if i == (target-i):
                    if nums.count(i)>1:
                        return [nums.index(i), nums.index(i, nums.index(i)+1)]
                else:
                    return [nums.index(i), nums.index(target-i)]

果然啊!成功!快了不少。
Runtime: 776 ms, faster than 37.75% of Python3 online submissions for Two Sum.
Memory Usage: 13.8 MB, less than 67.25% of Python3 online submissions for Two Sum.

(4) 基于index的间接查找问询法

        for i in range(len(nums)):
            if (target - nums[i]) in nums:
                if i==nums.index(target-nums[i]):
                    if nums.count(nums[i])>1:
                        return [i, nums.index(nums[i], i+1)]
                else:
                    return [i, nums.index(target-nums[i])]

果然啊!成功!保持在高水准。
Runtime: 788 ms, faster than 35.47% of Python3 online submissions for Two Sum.
Memory Usage: 13.7 MB, less than 85.58% of Python3 online submissions for Two Sum.

做一个回顾

首先恭喜自己,顺利通关。分数还行,Top 65%的位置,770ms,13.8MB,拿到了一颗星。

上述的四个方法,不管是“一手抓一手再抓”,还是“一手抓一手在摸”,在具体比较的时候都是“凭空”的,没有放在一个篮子或者秤上进行操作,总而言之就是有不可控的黑洞。

字典dictionary是一个比较喜欢的篮子。此外,几年前看过一个跷跷板的方法,结合起来:

(5) 基于item的字典dict跷跷板

        dict = {}
        for i in nums:
            if i in dict.keys():
                if i == dict.get(i):
                    return [nums.index(i), nums.index(i, nums.index(i)+1)]
                else:
                    # follow the queue number
                    return [nums.index(dict.get(i)), nums.index(i)]
            else:
                dict.update({target-i: i})

厉害了!成功!
Runtime: 36 ms, faster than 94.69% of Python3 online submissions for Two Sum.
Memory Usage: 14.2 MB, less than 50.40% of Python3 online submissions for Two Sum.

(6) 基于index的字典dict跷跷板

        dict = {}
        for i in range(len(nums)):
            if nums[i] in dict.keys():
                # ensure index return by the same number
                if i == nums.index(dict.get(nums[i])):
                    return [i, nums.index(dict.get(nums[i]), i+1)]
                else:
                    # follow the queue number
                    return [nums.index(dict.get(nums[i])), i]
            else:
                dict.update({target-nums[i]: nums[i]})

厉害了!不如上面的简洁。
Runtime: 40 ms, faster than 87.10% of Python3 online submissions for Two Sum.
Memory Usage: 14.2 MB, less than 53.05% of Python3 online submissions for Two Sum.

对比一下,新的分数还行,Top 5%的位置,36ms,14.2MB。

写在最后

最重要的测试集,用例,三个有代表性的列表,以及目标值:

[2, 7, 11, 15]
9
[3, 3]
6
[3, 2, 4]
6

第二波的对比第一波:排名从前63到了前5,耗时从776ms到36ms,内存从13.8MB到14.2MB。

最后的最后,剩下的几种解法呢?

简单来说,就是暴利查找的时候,第二层循环index下标从i+1开始啊!具体示例如下:

(7) 基于index的直接切片查找法

        for i in nums:
            for j in nums[nums.index(i):]:
                # find it!
                if (i+j)==target:
                    # maybe twice?
                    if i==j:
                        # more than 1 elements!
                        if nums.count(i)>1:
                            # don't forget find the 2nd one's index
                            return [nums.index(i), nums.index(i, nums.index(i)+1)]
                    # not twice
                    else:        
                        return [nums.index(i), nums.index(j)]

成功!比原来的速度略有提升。
Runtime: 3856 ms, faster than 26.13% of Python3 online submissions for Two Sum.
Memory Usage: 13.7 MB, less than 85.58% of Python3 online submissions for Two Sum.

(8) 基于index的间接切片问询法

        for i in range(len(nums)):
            # begin from i's index + 1, hope more and faster
            if (target - nums[i]) in nums[i+1:]:
                if i==nums.index(target-nums[i]):
                    if nums.count(nums[i])>1:
                        return [i, nums.index(nums[i], i+1)]
                else:
                    return [i, nums.index(target-nums[i])]

成功!但是速度并没有跷跷板的快。
Runtime: 836 ms, faster than 33.40% of Python3 online submissions for Two Sum.
Memory Usage: 13.8 MB, less than 66.48% of Python3 online submissions for Two Sum.

有几点需要说明:

  1. 忽略从i+1开始,是因为直接被[3,3]带歪思路了,看来用例有时候也是把双刃剑。得瑟啥?可悲。
  2. 第一次遇见跷跷板的时候,惊为天人,现在没那么惊吓了,还是赞叹。天外有天,山外有山。
  3. 原本以为加了i+1开始,找的越来越快,能跟跷跷板比一比。发现还是差了档次,应是dict的硬伤。
  4. 虽然说有8个解法,其实核心思想是这几个:暴力查找、差值问询、临时存取、缩减范围。
  5. 文中引用的数据应该不精准,看个定性就好。游戏也挺好玩的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容