哈希表理论基础
先来学习一下哈希表的基础知识,这部分之前了解不多。
数组本质就是一个哈希表,只不过索引是数字
哈希表用于快速判断一个元素是否出现在集合里,查询复杂度为O(1)
原数据经过hashFunction得到index,再对哈希表size取模,来避免超出size
哈希碰撞:拉链法,线性探测法
三种常用数据结构:数组,set,map
242 有效的字母异位词
题目链接
首先想到了最笨的思路,遍历字符串找到所有字母的重复次数并记录,然后对比两个字符串的记录结果是否相同,可以用字典,不过我不确定字典是否是有序的,先尝试一下
经过在jupyter notebook测试发现字典是无序的,内容相同但顺序不同的字典判断为相同
此思路一次成功通过,但耗时略长,60ms
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
dic1 = self.findDup(s)
dic2 = self.findDup(t)
if dic1 == dic2:
return True
else:
return False
def findDup(self, s: str):
dic = {}
for i in s:
if i not in dic:
dic[i] = 0
else:
dic[i] += 1
return dic
现在看一下解答,发现我的思路很接近python使用defaultdict函数的情况,不过结束的输出可以直接return dic1 == dic2,而无需判断true,false的
而且原来还有更方便的counter方法!尝试一下这个!耗时64ms
不过不管是defaultdict还是counter,都需要从collections包导入一下
再试下常规思路使用数组构建哈希表
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0]*26
record2 = [0]*26
for i in s:
record[ord(i) - ord('a')] += 1
for i in t:
record[ord(i) - ord('a')] -= 1
return record == record2
要记住ord函数用于取字符的ASCII码
349 两个数组的交集
题目链接
看到此题目,暴力解法就是逐个判断某元素是否在另一个数组里,然后去重
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
ans = set()
for i in nums1:
if i in nums2:
ans.add(i)
ans2 = list(ans)
return ans2
其中还是有个地方不清楚,就是如何把集合转为列表,用到了list函数,此思路通过
再思考一下有没有其他途径,有个笨方法是根据元素小于等于1000创建一个1000的数组,然后遍历有这个数字就在相应下标下+1,以空间换时间,试一下
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
record = [0]*1000
record2 = [0]*1000
ans = []
for i in nums1:
record[i] += 1
for i in nums2:
if record[i] > 0:
if i not in ans:
ans.append(i)
return ans
耗时减少了一点点,但内存空间居然没怎么变化,有点离谱,看看解答
两种方法和python的前两个解答类似,不过第三种方法挺巧妙的,利用&取交集的方法一行代码就实现了功能,不过测试了一下耗时比第二种方法长一点
202 快乐数
数字链接
这题有点复杂,乍一看没啥思路,应该需要先把数字的各位拆出来,然后按照要求不断计算,直到遇到循环或结果为1跳出
首先思考如何取出各位数字,可以除以10的最高位次幂,得到最高位数字,然后减去,再循环得到结果,试一下
实现此功能后思考按要求计算,最先想到的是递归调用原函数直到结果为1,但这样无法处理循环的情况,需要单独加一个判断
不对,递归无法判断循环,只能在一个函数中用while
class Solution:
def isHappy(self, n: int) -> bool:
s = n
record2 = []
while 1:
num = 1
while num <= s:
num *= 10
num /= 10 #找到了最高位
record = [] #把各位数字存到列表里
while num != 0.1:
a = int(s/num) #取出最高位数字
record.append(a)
s -= a*num
num /= 10
ans = 0
for i in record:
ans += i**2
if ans == 1:
return True
elif ans in record2:
return False
else:
record2.append(ans)
s = ans
判断逻辑是若结果为1则返回true,若结果在之前的记录中则证明出现了循环返回false,否则加入记录
看一下解答,python有很多种写法。。
首先学习python中单位操作的函数divmod(a,b),返回结果是a/b的商和余数,只需要b取10,再把商赋值给原数字,即可不断从小到大取得单位数字,比我自己写的从大到小取单位数字要快捷许多
还可以使用字符串!直接把数字转为字符串,然后就可以看作数组逐字符访问了!
判断是否重复可以用集合、数组、快慢指针相遇等多种方法!
1 两数之和
题目链接
此题比较直接的思路就是逐个访问任意两个元素的和,直到找到结果并返回
先对数组排序应该会提高效率,至少可以排除超过target的元素,然后把剩下的元素从两边取值来找到结果!但是要记得最后得返回index,还得再找一遍。。
排序函数不熟练啊,还得临时查,list.sort()无返回值,直接排序原列表,sorted(list),操作一个列表,返回一个排序后的新列表
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
nums2 = sorted(nums)
index1 = 0
index2 = len(nums2) - 1
ans = []
while 1:
sum = nums2[index1] + nums2[index2]
if sum == target:
ans.append(nums2[index1])
ans.append(nums2[index2])
break
elif sum > target:
index2 -= 1
elif sum < target:
index1 += 1
if ans[0] != ans[1]:
return [nums.index(ans[0]), nums.index(ans[1])]
else:
ans2= [nums.index(ans[0])]
temp = nums
temp[nums.index(ans[0])] += 1
ans2.append(nums.index(ans[1]))
return ans2
提交错了一次,遇到两个不同位置的相同元素时操作有误,不能把重复元素删掉,因为会改变index,而应该改变它的值
看一下解答,解答的思路比我还要更快一些,直接在遍历每个数字时寻找剩下的元素中有没有与其相加等于target的,再试着写一下这个思路:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(len(nums)):
temp = [j for j in nums]
temp.remove(nums[i])
nums2 = set(temp)
if target - nums[i] in nums2:
if target - nums[i] != nums[i]:
return [i, nums.index(target - nums[i])]
else:
temp2 = [j for j in nums]
temp2[i] += 1
return [i, temp2.index(target - nums[i])]
遇到了深拷贝浅拷贝的问题! 列表名直接赋值给另一个变量只是浅拷贝,修改两个变量会导致一个列表改变!被迫重新创建了两次列表,这样导致此代码运行时间很差,甚至和暴力法差不多,看一下解答代码
解答代码如何处理重复元素问题:判断出现重复元素后不改变元素,而使用切片访问不包含该元素的列表,重新获得index后再加上切片少的部分
还是字典方法比较好用,enumerate函数很方便
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
seen = set()
for index, value in enumerate(nums):
if target - value in seen:
return [index, nums.index(target-value)]
else:
seen.add(value)
甚至此代码无需考虑重复元素的问题,因为检查的都是之前见过的元素,重复元素只会在第二次遇到时发现,此时只需要返回当前的index和list.index()发现的即可
总结
熟悉了以下几种python方法:
- defaultdict() 创建一个特殊的字典,key不存在则返回一个默认值,参数为list,str,int等
- Counter() 函数,统计可迭代对象中各元素重复的次数,需要from collections import Counter,返回一个Counter对象,可以直接dict构建字典,也可以for k,v in Counter 来遍历访问
- ord() 用于取字符的ASCII码
- set创建的集合可以用&取交集,用|取并集,用-取差集运算
- divmod(a,b) 函数返回结果是a/b的商和余数,只需要b取10,再把商赋值给原数字,即可不断从小到大取得单位数字,也即 n, v = divmod(n, 10), v即为从小到大取出来的单位数字
- str(int) 可以直接把数字转换为字符串,如123转为’123’,如此便可以直接以列表形式访问单位数字
- list.sort() 直接对原list排序,返回值为空,类似的还有remove()函数,要注意这类无返回值的函数
- sorted(list)会返回一个新创建的列表
- 深浅拷贝问题!!a = [1,2,3],若赋值b=a,则a,b指向的是同一个列表!改变b会把a改变,这是浅拷贝,如果b = [i for i in a],则可以深拷贝一层,也即列表的第一维深拷贝了,但是二维列表的大于等于第二维仍然浅拷贝,因为实际存的是一个列表地址(b = a.copy()也一样的)。真正深拷贝只有import copy的copy.deepcopy()函数