1-两数之和

万里长征始于足下。

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

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

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/two-sum

解法 1:双重循环

乍一看,挺简单,不就两重循环么。说干就干。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        length = len(nums)
        ret = []
        isFind=False
        for i in range(length):
            for j in range(length):
                if i==j:
                    continue
                elif nums[i] + nums[j] == target:
                    isFind=True
                    ret.append(i)
                    ret.append(j)
                if isFind:
                    return ret
        return ret

这里再提供一个计算函数执行时间的装饰器(学以致用)

def print_func_time(function):
    @wraps(function)
    def func_time(*args, **kwargs):
        t0 = time.perf_counter()
        result = function(*args, **kwargs)
        t1 = time.perf_counter()
        print("Total running time: %s s" % (str(t1 - t0)))
        return result
    return func_time

当然,事情没有那么简单,显然而且必然的,在最后一组数据下超时了。

此处数据过长,就不贴了,自己去超时的地方看看就ok了

自己本地跑也是要 11-12s 左右的(视电脑不同而不同。)

Total running time: 12.628380413 s
[8010, 8011]

暂时没有什么错误发生,那么接下来就是考虑优化的事情了。

  • 首先就是我们的 ret 不必要存在,可以直接返回的时候封装成list就可以了,可以省掉一点 append 操作的时间。

  • 然后就是我们的 isFind 判断是不必要的,题目中已经假设了必然有且只有一个结果,我们发现结果的时候直接返回就可以了。

  • 再接着,我们的j不必要从头开始遍历,会造成冗余的循环。只要从i+1的地方开始就可以了。

修改后的代码长这样:

def twoSum(self, nums: List[int], target: int) -> List[int]:
    length = len(nums)
    for i in range(length):
        for j in range(i+1,length):
            if nums[i] + nums[j] == target:
                return [i,j]
    return []

时间下降了很多:

Total running time: 6.893857144 s
[8010, 8011]

当然,这显然不符合我们的要求,虽然题目不说,但是,我们肯定要压缩到 1s 以内的。

考虑到,普通 index 的遍历速度 < enumerate <iterator。

但是我们这边需要我们遍历的索引。所以我们考虑使用 enumerate 而不是使用 iterator。

Python 中加法操作好像比较耗时,在大量操作后,iterator 的操作时间已经比 enumerate 直接取索引高很多了。

所以不考虑使用 iterator,在迭代内部计算索引。(实操证明过了,不信可以自己实验)

时间再次下降 1 秒:

def twoSum(self, nums: List[int], target: int) -> List[int]:
        length = len(nums)
        # newNums = sorted(enumerate(nums),key=lambda x:x[1])
        for i,item in enumerate(nums):
            for j in range(i+1,length):
                if item+nums[j] == target:
                    return [i,j]
Total running time: 5.895048940000001 s
[8010, 8011]

这时候我们再考虑,target值是两者相加得到(数据组中有负数存在需要考虑)。

  • target>=0的情况下,必然有一个数是小于target
  • target<0的情况下,必然有一个数是大于target

那么,我们可以先对数组进行一下排序,以对循环进行剪枝:

def twoSum(self, nums: List[int], target: int) -> List[int]:
        length = len(nums)
        newNums = sorted(enumerate(nums),key=lambda x:x[1])

        if target >= 0:
            for i in range(length):
                if i > target:
                    break
                for j in range(i + 1, length):
                    if newNums[i][1] + newNums[j][1] == target:
                        return [newNums[i][0], newNums[j][0]]
            return []
        elif target < 0:
            for i in range(length):
                if i < target:
                    break
                for j in range(i + 1, length):
                    if newNums[i][1] + newNums[j][1] == target:
                        return [newNums[i][0], newNums[j][0]]
            return []

我们发现已经低于 0.01 秒了。已经能通过 LeetCode 的提交了。但是你还是 Too young too simple。

那是因为我们的数据有点特殊,我们在此把target替换成(25196+25194)= 50390

我们发现,时间还是超时了(当然是本地跑的,10s 左右)

Total running time: 10.42657594 s
[12597, 12598]

当然不能因为特殊而特殊啦

本着科学研究的精神,我们进行一下升华

解法 2:哈希表

我们反过来考虑一下,我们何苦去它有的里面找有两个数相加得到目标值呢。凭啥听他的一定要到它里面找。

(当然得从里面找)

但是,我们可以考虑一下,我从list中取第i个值,然后计算出target-list[i],然后我去 list 里面找,我计算出来的这个差值是否在 list 中,如果在,那就对了,如果不在,就取i+1进行下一步运算。

当然不能用双重循环啦,那跟第一种方法没区别了。

我们考虑到哈希表,Python 中内置的字典dict就是一个哈希表:

def twoSum(self, nums: List[int], target: int) -> List[int]:
        hashmap = {}
        for index, num in enumerate(nums):
            another_num = target - num
            if another_num in hashmap:
                return [hashmap[another_num], index]
            hashmap[num] = index
        return None

同时我们边存边判断,这样可以减少一次循环。

你可能考虑到,哈希表,不存在重复值,但是我的数组可能存在重复值,会不会有影响。

看代码:

  1. 我们外层循环是对enumerate(nums)进行循环的,所以每一个原有的值都会遍历到
  2. 题目中有说到有且只有一个答案。那么,重复值引起的有两种选择的情况也是不存在的。