哈希表系列1
- 哈希表理论基础
- 242 有效的字母异位词
- 读题小记
- 代码随想录的代码
- 力扣的示例代码
- 基于242的拓展题目--383赎金信
- 我的代码
- 力扣的示例代码
- 代码随想录的代码
- 49 字母异位词分组
- 力扣中录友的代码
- 我的代码
- 力扣的示例代码
- 438 找到字符串中所有字母异位词
- 我的代码
- 力扣录友的代码
- 力扣的示例代码
- 349 两个数组的交集
- 代码随想录的代码
- 力扣的示例代码
- 350 两个数组的交集 II
- 我的代码
- 力扣的示例代码
- 202 快乐数
- 我的代码
- 代码随想录的代码
- 力扣的示例代码
哈希表理论基础
哈希表之前从没学过,也不知道怎么在编程语言中使用,理论基础没什么要总结的,因为代码随想录中的知识点我都是第一次接触。这里把链接列出来。
哈希表理论基础
242 有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。(s,t仅包含小写字母)
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
示例1:
输入: s = “anagram”, t = “nagaram”
输出: true
示例2:
输入: s = “rat”, t = “car”
输出: false
读题小记
第一次做哈希表的相关题目,不知道如何想,不知道代码如何编写,直接学习代码随想录的想法,并抄写代码。
242 有效的字母异位词
代码随想录的代码
Python写法一:使用数组
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0] * 26
for i in s:
#并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
record[ord(i) - ord("a")] += 1
for i in t:
record[ord(i) - ord("a")] -= 1
for i in range(26):
if record[i] != 0:
#record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return False
return True
Python写法二(没有使用数组作为哈希表,只是介绍defaultdict这样一种解题思路):
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
from collections import defaultdict
s_dict = defaultdict(int)
t_dict = defaultdict(int)
for x in s:
s_dict[x] += 1
for x in t:
t_dict[x] += 1
return s_dict == t_dict
Python–defaultdict学习:
defaultdict是对Python中字典dict的改善。
如果是字典dict:用法是dict={},添加元素是dict[element]=value,调用是dict[element],但是前提是element是存在于字典的,不然会报错误KeyError错误。
对于这种情况,defaultdict就可以避免这个错误,defaultdict的作用是在于,当字典里的element不存在但被查找时,返回的不是keyError而是一个默认值,返回的是工厂函数的默认值,比如list对应[ ],str对应的是空字符串,set对应set( ),int对应0。
Python写法三(没有使用数组作为哈希表,只是介绍Counter这种更方便的解题思路):
class Solution(object):
def isAnagram(self, s: str, t: str) -> bool:
from collections import Counter
a_count = Counter(s)
b_count = Counter(t)
return a_count == b_count
Python–Counter学习:
详细内容知乎链接。
Counter
力扣的示例代码
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
ji = set(s)
if ji != set(t):
return False
for d in ji:
if s.count(d) != t.count(d):
return False
return True
基于242的拓展题目–383赎金信
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。(ransomNote 和 magazine 由小写英文字母组成)
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
示例1:
输入:ransomNote = “a”, magazine = “b”
输出:false
示例2:
输入:ransomNote = “aa”, magazine = “ab”
输出:false
示例3:
输入:ransomNote = “aa”, magazine = “aab”
输出:true
我的代码
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
record = [0]*26
for i in magazine :
record[ord(i)-ord('a')] += 1
for i in ransomNote :
record[ord(i)-ord('a')] -= 1
for i in range(26):
if record[i] < 0:
return False
return True
力扣的示例代码
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
set_ransomNote = list(set(ransomNote))
for i in set_ransomNote:
if ransomNote.count(i)>magazine.count(i):
return False
return True
代码随想录的代码
版本一:使用数组
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
ransom_count = [0] * 26
magazine_count = [0] * 26
for c in ransomNote:
ransom_count[ord(c) - ord('a')] += 1
for c in magazine:
magazine_count[ord(c) - ord('a')] += 1
return all(ransom_count[i] <= magazine_count[i] for i in range(26))
版本二:使用defaultdict
from collections import defaultdict
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
hashmap = defaultdict(int)
for x in magazine:
hashmap[x] += 1
for x in ransomNote:
value = hashmap.get(x)
if not value or not value:
return False
else:
hashmap[x] -= 1
return True
版本三:使用字典
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
counts = {}
for c in magazine:
counts[c] = counts.get(c, 0) + 1 # get(c, 0)不存在则返回0,如果不给第二个参数,不存在返回KeyError
for c in ransomNote:
if c not in counts or counts[c] == 0:
return False
counts[c] -= 1
return True
版本四:使用Counter
from collections import Counter
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
return not Counter(ransomNote) - Counter(magazine)
版本五:使用count
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
return all(ransomNote.count(c) <= magazine.count(c) for c in set(ransomNote))
49 字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例1:
输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出: [[“bat”],[“nat”,“tan”],[“ate”,“eat”,“tea”]]
示例2:
输入: strs = [“”]
输出: [[“”]]
示例3:
输入: strs = [“a”]
输出: [[“a”]]
力扣中录友的代码
版本一:用了Python内置函数sorted,那时间复杂度就是O(n^2 logn)了吧。
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
res = []
dic = {}
for s in strs:
keys = "".join(sorted(s))
if keys not in dic:
dic[keys] = [s]
else:
dic[keys].append(s)
return list(dic.values())
版本二:用质数表示26个字母,把字符串的各个字母相乘,这样可保证字母异位词的乘积必定是相等的。其余步骤就是用map存储。
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
map = {
'a':2,'b':3,'c':5,'d':7,'e':11,'f':13,'g':17,'h':19,'i':23,'j':29,'k':31,'l':37,'m':41,
'n':43,'o':47,'p':53,'q':59,'r':61,'s':67,'t':71,'u':73,'v':79,'w':83,'x':89,'y':97,'z':101
}
resmap={}
reslist = []
for str in strs:
m = 1
for i in range(len(str)):
m*=map[str[i]]
if not m in resmap:
resmap[m]=[]
resmap[m].append(str)
print(resmap.values())
return [j for j in resmap.values()]
我的代码
第一次做这道题的时候,想的还是像前面题使用的逻辑,先对每一个字符串建立一个26长度的record,然后通过两层循环,去判断是否相等。
但是这样写,时间复杂度就有些高了,因为判断是否相等这一个操作也是O(n)的。
这种写法,不仅空间复杂度大,时间复杂度也大。
就不写出这种垃圾代码了。
力扣的示例代码
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
d = {}
for st in strs:
key = ''.join(sorted(st))
if key in d:
d[key].append(st)
else:
d[key] = [st]
return list(d.values())
438 找到字符串中所有字母异位词
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。(s 和 p 仅包含小写字母)
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
示例1:
输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。
示例2:
输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的异位词。
我的代码
无敌耗时的代码,没有任何算法技巧可言,竟然通过了???我不能接受。
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
slen = len(s)
plen = len(p)
p = sorted(p)
res = []
if slen < plen :
return res
else :
diff = slen - plen
for i in range(diff+1):
temp = s[i:i+plen]
if sorted(temp) == p :
res.append(i)
return res
力扣录友的代码
翻了前两页的评论,就找到这一个Python3的,和力扣的示例代码逻辑一致,还是要灵活学习运用哈希的思想。
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
if len(s) < len(p):
return []
Num = []
n = len(p)
A = [0] * 26
for i in range(n):
A[ord(p[i]) - ord('a')] += 1
A[ord(s[i]) - ord('a')] -= 1
if A == [0] * 26:
Num.append(0)
for i in range(n, len(s)):
A[ord(s[i]) - ord('a')] -= 1
A[ord(s[i - n]) - ord('a')] += 1
if A == [0] * 26:
Num.append(i + 1 - n)
return Num
力扣的示例代码
看了示例代码,还是这个代码好啊,时间复杂度低,而且也是应用了哈希的思想,学习!!!
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
s_len, p_len = len(s), len(p)
if s_len < p_len:
return []
ans = []
s_count = [0] * 26
p_count = [0] * 26
for i in range(p_len):
s_count[ord(s[i]) - 97] += 1
p_count[ord(p[i]) - 97] += 1
if s_count == p_count:
ans.append(0)
for i in range(s_len - p_len):
s_count[ord(s[i]) - 97] -= 1
s_count[ord(s[i + p_len]) - 97] += 1
if s_count == p_count:
ans.append(i + 1)
return ans
349 两个数组的交集
给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的
代码随想录的代码
要注意,使用数组来做哈希的题目,是因为题目都限制了数值的大小。
而这道题目没有限制数值的大小,就无法使用数组来做哈希表了。
而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
此时就要使用另一种结构体了,set 。
直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。
不要小瞧 这个耗时,在数据量大的情况,差距是很明显的。
(版本一) 使用字典和集合
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 使用哈希表存储一个数组中的所有元素
table = {}
for num in nums1:
table[num] = table.get(num, 0) + 1
# 使用集合存储结果
res = set()
for num in nums2:
if num in table:
res.add(num)
del table[num]
return list(res)
(版本二) 使用数组
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
count1 = [0]*1001
count2 = [0]*1001
result = []
for i in range(len(nums1)):
count1[nums1[i]]+=1
for j in range(len(nums2)):
count2[nums2[j]]+=1
for k in range(1001):
if count1[k]*count2[k]>0:
result.append(k)
return result
(版本三) 使用集合
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
力扣的示例代码
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# return list(set(nums1) & set(nums2))
return list(set(nums1).intersection(set(nums2)))
350 两个数组的交集 II
给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
我的代码
有了前一道题的铺垫,这道题做起来就很简单,因为输出结果可以重复,所以set方法是不能用的,要用字典数据结构作为哈希base。
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
table = {}
for num in nums1:
table[num] = table.get(num, 0) + 1
# 使用集合存储结果
res = []
for num in nums2:
if num in table:
res.append(num)
table[num] -= 1
if table[num] == 0:
del table[num]
return res
力扣的示例代码
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
ret = []
len1 = len(nums1)
len2 = len(nums2)
if len1 <= len2:
first = nums1
second = nums2
else:
first = nums2
second = nums1
hash_table = {}
for i, v in enumerate(first):
if v not in hash_table:
hash_table[v] = 1
else:
hash_table[v] += 1
for w in second:
if w in hash_table and hash_table[w] > 0:
ret.append(w)
hash_table[w] -= 1
return ret
202 快乐数
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
示例1:
输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例2:
输入:n = 2
输出:false
我的代码
第一次看题,有一个一直困扰我思路的问题是:我如何加入一个判断条件来避免无限循环的出现?
后来看了解析发现是读题问题。。。题中说了,如果不是快乐数,一定会出现无限循环。。。那这样就可以判断了。
自己写的垃圾代码,时间上才击败了7%。
class Solution:
def isHappy(self, n: int) -> bool:
sum_list = []
count = 0
while count not in sum_list and count != 1:
count = self.every_sum(n)
sum_list.append(count)
n = count
count = self.every_sum(n)
if count == 1:
return True
else :
return False
def every_sum(self,n:int) -> int :
count = 0
while n > 0 :
rem = n % 10
count = count + rem * rem
n = n // 10
return count
代码随想录的代码
(版本一)使用集合
class Solution:
def isHappy(self, n: int) -> bool:
record = set()
while True:
n = self.get_sum(n)
if n == 1:
return True
# 如果中间结果重复出现,说明陷入死循环了,该数不是快乐数
if n in record:
return False
else:
record.add(n)
def get_sum(self,n: int) -> int:
new_num = 0
while n:
n, r = divmod(n, 10)
new_num += r ** 2
return new_num
(版本二)使用集合
class Solution:
def isHappy(self, n: int) -> bool:
record = set()
while n not in record:
record.add(n)
new_num = 0
n_str = str(n)
for i in n_str:
new_num+=int(i)**2
if new_num==1: return True
else: n = new_num
return False
(版本三)使用数组
class Solution:
def isHappy(self, n: int) -> bool:
record = []
while n not in record:
record.append(n)
new_num = 0
n_str = str(n)
for i in n_str:
new_num+=int(i)**2
if new_num==1: return True
else: n = new_num
return False
(版本四)使用快慢指针
class Solution:
def isHappy(self, n: int) -> bool:
slow = n
fast = n
while self.get_sum(fast) != 1 and self.get_sum(self.get_sum(fast)):
slow = self.get_sum(slow)
fast = self.get_sum(self.get_sum(fast))
if slow == fast:
return False
return True
def get_sum(self,n: int) -> int:
new_num = 0
while n:
n, r = divmod(n, 10)
new_num += r ** 2
return new_num
(版本五)使用集合+精简
class Solution:
def isHappy(self, n: int) -> bool:
seen = set()
while n != 1:
n = sum(int(i) ** 2 for i in str(n))
if n in seen:
return False
seen.add(n)
return True
(版本六)使用数组+精简
class Solution:
def isHappy(self, n: int) -> bool:
seen = []
while n != 1:
n = sum(int(i) ** 2 for i in str(n))
if n in seen:
return False
seen.append(n)
return True
力扣的示例代码
class Solution:
#不是快乐数的数称为不快乐数(unhappy number),所有不快乐数的数位平方和计算,最後都会进入 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 的循环中。
def isHappy(self, n: int) -> bool:
while True:
s=str(n)
sum=0
for item in s:
sum+=int(item)*int(item)
if sum==1:
return True
if sum==4:
return False
n=sum