本文主要讲解了LeetCode_01,Two Sum的几种解法。博主是直接暴力求解的,在参考了比较好的解法之后,作了一定的讲解,分享于此。讲解部分是我自己的理解,方法一到方法四,如果你点开了,请耐心读读,欢迎交流。
知识尚浅可能会有遗漏或错误之处,欢迎指正。
20.10.27,10:00-16:00——初稿
20.10.28,9:00-11:30——完善算法理解,添加评价,修改标题
用的时间比较多,因为是刚开始刷leet,所以反复推敲了下,也方便自己总结套路(其实效率有点低了,不过我相信之后会慢慢快起来的)。
——梦开始的地方
题目描述
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
方法一、暴力求解
Java解法
算法思路:以第一个数为基准开始遍历,找到数列中剩余数中与其作和等于target的数,找到则存储下标到事先定义好的arr整型数组中,否则继续查找。最后返回数组。
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] arr = new int[2];
for(int i=0;i<nums.length;i++){
for(int j = i+1;j<nums.length;j++){
if(nums[j] == target - nums[i]){
arr[0] = i;
arr[1] = j;
}
}
}
return arr;
}
}
评价
因为是先后以第一个、第二个…为基准向后查找,所以很轻松的覆盖的所有的情况。很常见的解题方案,时间复杂度为O(n^2),最坏情况下数组中任意两个数都要被匹配一次。从执行结果来看,执行用时88ms,空间消耗低,效果还不错。
方法二、用 Python 中 list 的相关函数求解
Python解法
代码写的有些繁琐,这里提前讲解代码中涉及到的知识。
Python count() 方法用于统计字符串里某个字符出现的次数。可选参数为在字符串搜索的开始与结束位置。
&,位运算,同真(1)为真(1)否则为假(0)
关于index的用法,发现网上很多参考都没有讲清楚,可以戳【Python】index()用法
Python continue 语句跳出本次循环,而break跳出整个循环。
continue 语句用来告诉Python跳过当前循环的剩余语句,然后继续进行下一轮循环。
算法思路:判断原数组nums中是否可以找到target与遍历数(初始从第一个数开始)的作差。如果找到了基准数本身(就比如[4,5,11,12,3] t = 8 ,nums.count(8-4) == 1&(8-4 == 4)说明只有本身能满足,后续的数都不能与其作和等于target),则从下一个数5开始循环。
否则遇到以下的情况则直接则从基准数后面的序列中查找
也就是位相与的相反的三种情况
①遇到nums.count(num2)等于二的情况,比如4,5,7,4 target = 8
②遇到num2 != nums[i],比如4,5,7,11 target = 9
③不存在两种都成立
所以就是以上这两种情况了。
class Solution(object):
def twoSum(self,nums, target):
lens = len(nums)
j=-1
for i in range(lens):
num2 = target - nums[i]
if (num2) in nums:#nums中找到target与遍历数的差
if (nums.count(num2) == 1)&(num2 == nums[i]):#如果num2=num1,且nums中只出现了一次,说明找到了num1本身。就比如[4,5,11,12] t = 8 ,nums.count(8-4) == 1&(8-4 == 4),说明找到了本身4
continue
else:
j = nums.index(num2,i+1) #index(x,i+1)是从num1后的序列后找num2
break
return [i,j]
评价
最坏情况是从num1(也就是num[i])后面的序列找num2时,4,5,7,4 target=8这种情况,也就是后面序列都要索引一遍,所以时间复杂度依然是O(n^2)。算法耗时较大。耗时较大的原因我觉得是每次找num2时是从整个序列nums里面找的,这里还包括了num1。所以考虑优化一下。
优化
仔细想想,num2 的查找并不需要每次从 nums 查找一遍,只需要从 num1 位置之前或之后查找即可。但为了方便 index 这里选择从 num1 位置之前查找。
class Solution(object):
def twoSum(self,nums, target):
lens = len(nums)
j=-1
for i in range(1,lens):
temp = nums[:i]
if (target - nums[i]) in temp:#num1之前中找target与遍历数的差
j = temp.index(target - nums[i])
break
return [j,i]
评价
可以看到执行用时降低了近一半。我们优化的同时一定要考虑重复数的情况,考虑这三个例子
①[3,3] t = 6
②[3,5,7,3] t = 6
③[5,3,7,3] t = 6
都可以完美通过,这种优化方案的优点在于,使用temp[:i]记录了i之前的数,没有打乱数的序号。如果是向后查找,对于nums = [3,3]的情况,使用temp[i:],temp=[3],本身nums中序号为1的3到了temp中序号变成了0,这也是下面的疑问的来源。关于向后查找有没有比较好的思路,欢迎大佬评论~
疑问
这个优化方案是从 num1 位置之前查找。那么为什么不能从num1位置之后查找呢?
class Solution(object):
def twoSum(self,nums, target):
lens = len(nums)
j=-1
for i in range(0,lens):
temp = nums[i+1:lens]
if (target - nums[i]) in temp:
j = nums.index(target - nums[i])
break
else:
continue
return [i,j]
但是对于这种[3,3] target= 6,并不好使,因为从temp中索引到的3在sums中的索引为1,我们是希望它的索引是2的。所以这里不得不考虑重复的情况。思去想来,好像又回到了解放前。
另外,我们发现前面的方法在找num2的时候都没有把序号和数值绑定在一起,所以我们考虑用到python enumerate()这种方法在查找的时候将序号和数值绑定在一起。
方法三、用字典模拟哈希求解
算法思路:通过哈希来求解,这里通过字典来模拟哈希查询的过程。类似方法二,字典记录了 num1 和 num2 的值和位置,而省了再查找 num2 索引的步骤。
Python enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
Python 字典(Dictionary) get() 函数返回指定键的值。如果键不在字典中返回默认值 None 或者设置的默认值。这里是返回None。
Python解法
注:为了简化代码,num2在本代码中为cp,num1就是遍历的数
class Solution(object):
def twoSum(self,nums, target):
dct={}
for i,n in enumerate(nums):
dct[n] = i
for i,n in enumerate(nums):
cp = target - n
j = dct.get(cp)
if j is not None and i!=j:
return [i,j]
比如2,5,7,11 t=9这个例子
评价
耗时28ms,还可以的。因为是通过字典来仿哈希查询,查字典用的复杂度较大,所以可以优化为直接用哈希表。
方法四、哈希表
算法思路:方法一二的时间复杂度较高的原因是寻找num2 (target-num1)的时间复杂度过高。因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。同时这个方法也是方法三的优化。
使用哈希表,可以将寻找 num2 的时间复杂度降低到从 O(N)降低到 O(1)。
这样我们创建一个哈希表,对于每一个num1,我们首先查询哈希表中是否存在num2,然后将num1 插入到哈希表中,即可保证不会让 num1和自己匹配。
Python解法
注:为了简化代码,num2在本代码中为cp,num1就是遍历的数
class Solution(object):
def twoSum(self,nums, target):
dct = {}
for i, n in enumerate(nums):
cp = target - n
if cp in dct:
return [dct[cp], i]
else:
dct[n] = i
比如2,5,7,11 t=9这个例子
也可以参考官方给的代码
python
class Solution:
def twoSum(self, nums,target):
hashtable = dict()
for i, num in enumerate(nums):
if target - num in hashtable:
return [hashtable[target - num], i]
hashtable[nums[i]] = i
return []
java
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> hashtable = new HashMap<Integer,Integer>();
for(int i = 0;i<nums.length;i++){
if(hashtable.containsKey(target - nums[i])){
return new int[]{hashtable.get(target - nums[i]),i};
}
hashtable.put(nums[i],i);
}
return new int[0];
}
}
评价
使用哈希表,可以将寻找 num2 的时间复杂度降低到从 O(N)降低到 O(1)。耗时16ms,推荐此方法。
总结
本文主要讲解了LeetCode_01,Two Sum的几种解法。
比较推荐第三种优化解法,比较快一些,算法也比较好理解。
刚开始刷题,通常只能想到暴力求解。因为做的太少了。刷题也是为了有更好的解法,节省时间和空间。见的多自然就会用更好的方法。
参考
Python enumerate() 函数
python object类
读懂python中的self
Python 字典(Dictionary) get()方法
博主比较小白,但是热爱分享。一直感觉自己写代码的能力,算法能力都不太行,所以最近开始刷LeetCode,一方面记录方便自己学习,另一方面给需要的同伴参考。虽然失败并不可怕,但是也希望同伴们少踩一些坑。分析算法挺费劲的,留个赞或评论支持一下博主吧!同时我也非常希望写出更通俗易懂的文章,知识尚浅,如有遗漏或错误,欢迎指正~