LeetCode #1 - Two Sum
题目链接:https://leetcode.com/problems/two-sum/description/
给你一个整数数组nums,以及一个整数target,要求从nums中找到两个和为target的数,返回这两个数的下标(indices)。
你可以认为每个输入有且仅有唯一解,且nums中每个数字只能使用一次。
样例:
给出 nums = [2, 7, 11, 15], target = 9, 因为 nums[0] + nums[1] = 2 + 7 = 9, 所以结果是 [0, 1].
久别竞赛思维,再加上看到难度是easy,以为这里对程序时间复杂度不会有太高要求,自然而然想到了暴力搜索,于是很快写好了如下的python代码:
def twoSum(nums, target): for i in range(len(nums) - 1): for j in range(len(nums) - i - 1): if nums[i] + nums[i + j + 1] == target: indic = [i, i + j + 1] break return indic
简单直接易懂的代码。
提交,结果TLE。
只能改进算法了,然而高中搞竞赛时候就没把数据结构和算法学好,又好几年没碰,完全没有思路。无奈之下求助于官方solution,第一种解法就是java实现的暴搜。第二种就是哈希了。对哈希还是有点印象的,用空间换时间,达到O(1)的查找。可是怎么实现呢,自己写哈希函数的话,又有点捉急了-_-||。再看官方给出的代码,是直接使用java内置的hashmap类实现的。
诶?python不是也自带一个基于哈希算法的数据类型——字典类型(dict)嘛,那就用dict来实现一下吧。
回顾一下暴搜的思路:选定一个元素nums[i],然后从它后面一个元素开始挨个尝试,看两者加起来是不是等于target。既然target这个东西是已知的,那我们在选定nums[i]以后是否可以不挨个试,而是直接查询nums数组里有没有 target - nums[i] 这个东西(就是互补部分,complement),如果查询到,就得到一个解,根据题目描述这是唯一解,就可以直接break返回了。说到查询数组里有没有一个数,用python的话第一反应是通过in判断,可不要忘了这个方法其实就是遍历数组,如果这么做那就又回到暴搜上了。
如何用dict来实现呢?我们先遍历一次nums,把nums[i]作为key,下标 i 作为value,存储到字典d中。然后再次遍历nums,对每个nums[i],查询它的complement是否在字典d里,如果存在,说明得到唯一解,输出当前元素的下标 i 和complement的下标即d[target - nums[i]]两个数组成的列表。
但这还没完,有一种特殊情况是target由两个相等的数相加得到,如nums = [3, 3], target = 6。在python中,字典类型的key是唯一的,不允许有重复。所以我们需要单独写一段代码应对这种情况。完整代码如下:
def twoSum(nums, target): if target / 2 in nums: #判断特殊情况 l = [] for i in range(len(nums)): if nums[i] == target / 2: l.append(i) if len(l) == 2: #还得判断是否得到了两个数,因为还有这种情况:[3, 2, 4] --> 6 return l d = {} for i in range(len(nums)): d[nums[i]] = i for i in range(len(nums)): if nums[i] != target - nums[i]: #还是[3, 2, 4] --> 6这种情况,没有这一条件将输出[0, 0]
continue if d.get(target - nums[i]) != None: return [i, d[target - nums[i]]]
提交,AC。
其实我们可以只用一次循环,边填字典边查询:虽然字典没编写完,但我们利用已经编好的部分可以查到当前元素的complement是否在前面出现过了。不同之处在于,上面的两次循环得到解时,i 是在nums中位置靠前的数的下标,而一次循环得到得到解时 i 是位置靠后的数的下标,所以最后返回值里两个表达式也要颠倒一下。代码如下:
#前面的部分不变 for i in range(len(nums)): if nums[i] == target - nums[i]: continue d[nums[i]] = i if d.get(target - nums[i]) != None: return [d[target - nums[i]], i]
不过少这一次循环对程序运行时间根本没啥影响。