3 哈希
哈希表(Hash Table),也称为散列表,是一种常用的数据结构,用于实现键-值(key-value)对的高效存储和查找。它通过将键映射到一个固定大小的数组(称为哈希表)中的索引位置来实现快速的访问。
哈希表的基本思想是利用哈希函数(Hash Function)将键转换成数组索引,使得每个键都可以在数组中找到唯一的位置。当需要插入或查找一个键时,通过哈希函数计算出相应的索引,然后在该索引位置上进行操作
3.1 找出变位映射
题目描述
给定两个列表 A
and B
,并且 B
是 A
的变位(即 B
是由 A
中的元素随机排列后组成的新列表)。
我们希望找出一个从 A
到 B
的索引映射 P
。一个映射 P[i] = j
指的是列表 A
中的第 i
个元素出现于列表 B
中的第 j
个元素上。
列表 A
和 B
可能出现重复元素。如果有多于一种答案,输出任意一种。
例如,给定
A = [12, 28, 46, 32, 50]
B = [50, 12, 32, 46, 28]
需要返回
[1, 4, 3, 2, 0]
P[0] = 1 ,因为 A 中的第 0 个元素出现于 B[1],而且 P[1] = 4 因为 A 中第 1 个元素出现于 B[4],以此类推。
分思路析
以 A=[12,28,46],B=[46,12,28]
为例。我们想知道 12,28,46
在 B
中的位置,如果有一个字典(哈希表)D = {12: 1, 28: 2, 46: 0}
,那么这个问题可以很轻松的被解决。
实现代码
1.遍历+查询
class Solution:
def anagramMappings(self, A: List[int], B: List[int]) -> List[int]:
n = len(B)
# defaultdict是一个类型于字典的数据结构,它是collections模块提供的一个类
# 当我们访问字典中不存在的键时,defaultdict会根据指定的默认值类型自动创建并初始化键,
#而不会抛出KeyError异常。这使得我们可以更方便地处理某些特定的字典操作,例如计数、分组等
num_ids = defaultdict(list)
for index, value in enumerate(B):
num_ids[value].append(index)
# num_ids为 value:index
res = []
for x in A:
# 遍历A的每个元素, 即为num_ids的索引
# pop()用于删除指定键对应的键值对,并返回该键对应的值
tmp = num_ids[x].pop(0) #返回x对应B中的索引
res.append(tmp)
return res
2.简单写法,但时间复杂度较高
map的用法
map(function, iterable)
其中,function 是要应用的函数,iterable 是一个或多个可迭代对象(如列表、元组等)
def square(x):
return x ** 2
numbers = [1, 2, 3, 4, 5]
squared_numbers = map(square, numbers)
print(list(squared_numbers)) # 输出: [1, 4, 9, 16, 25]
def add(x, y):
return x + y
numbers1 = [1, 2, 3]
numbers2 = [10, 20, 30]
result = map(add, numbers1, numbers2)
print(list(result)) # 输出: [11, 22, 33]
class Solution:
def anagramMappings(self, A: List[int], B: List[int]) -> List[int]:
#list.index()用于在列表中查找指定元素的索引位置,并返回第一次出现该元素的索引值。
return list(map(B.index, A))
3.2 回文排列
题目描述
给定一个字符串,判断该字符串中是否可以通过重新排列组合,形成一个回文字符串。
示例 1:
输入: "code"
输出: false
示例 2:
输入: "aab"
输出: true
示例 3:
输入: "carerac"
输出: true
分思路析
哈希 字典统计 每个字母出现次数
且最多允许一个字母出现奇数次
实现代码
class Solution:
def canPermutePalindrome(self, s: str) -> bool:
char_freq = defaultdict(int)
#d = defaultdict(int) print(d['key']) # 输出: 0
for c in s:
char_freq[c] += 1
cnt = 0
for c, freq in char_freq.items():
if freq % 2 == 1:
cnt += 1
if cnt > 1: #只允许一个字母出现奇数次
return False
return True
class Solution:
def canPermutePalindrome(self, s: str) -> bool:
dic = {}
for i in s:
dic[i] = dic.get(i)+1
#dict.get(i, default) 是针对字典(dict)对象的方法调用,用于获取指定键 i 的值。
# 如果键 i 存在于字典中,则返回与该键关联的值;如果键 i 不存在于字典中,则返回预设值 default。
# 没有default 返回None
count = 0
for i in dic.values():
if i%2==1:
count+=1
return count<2
3.3 句子相似性
题目描述
我们可以将一个句子表示为一个单词数组,例如,句子 "I am happy with leetcode"
可以表示为 arr = ["I","am",happy","with","leetcode"]
给定两个句子 sentence1
和 sentence2
分别表示为一个字符串数组,并给定一个字符串对 similarPairs
,其中 similarPairs[i] = [xi, yi]
表示两个单词 xi
and yi
是相似的。
如果 sentence1
和 sentence2
相似则返回 true
,如果不相似则返回 false
。
两个句子是相似的,如果:
它们具有 相同的长度 (即相同的字数)
sentence1[i]
和 sentence2[i]
是相似的
请注意,一个词总是与它自己相似,也请注意,相似关系是不可传递的。例如,如果单词 a 和 b 是相似的,单词 b 和 c 也是相似的,那么 a 和 c 不一定相似 。
示例 1:
输入: sentence1 = ["great","acting","skills"], sentence2 = ["fine","drama","talent"],
similarPairs = [["great","fine"],["drama","acting"],["skills","talent"]]
输出: true
解释: 这两个句子长度相同,每个单词都相似。
示例 2:
输入: sentence1 = ["great"], sentence2 = ["great"], similarPairs = []
输出: true
解释: 一个单词和它本身相似。
示例 3:
输入: sentence1 = ["great"], sentence2 = ["doubleplus","good"],
similarPairs = [["great","doubleplus"]]
输出: false
解释: 因为它们长度不同,所以返回false。
分思路析
哈希 字典统计 每个字母出现次数;且最多允许一个字母出现奇数次
实现代码
class Solution:
def areSentencesSimilar(self, sentence1: List[str], sentence2: List[str], similarPairs: List[List[str]]) -> bool:
n1 = len(sentence1)
n2 = len(sentence2)
#先判断sentence1和sentence2长度是否相等,若不同返回False
if n1 != n2:
return False
dic = defaultdict(set)
for x, y in similarPairs:
dic[x].add(y)
dic[y].add(x)
flag = True
#zip() 函数用于将多个可迭代对象(例如列表、元组等)中对应位置的元素打包成一个元组
for x, y in zip(sentence1, sentence2):
if x == y:
continue
if x in dic[y]:
continue
flag = False
break
return flag
class Solution:
def areSentencesSimilar(self, sentence1, sentence2, similarPairs):
if len(sentence1) != len(sentence2):
return False
#将similarPairs转化为hashset,可以加快匹配查找的速度
sim = set(map(tuple, similarPairs))
#map(tuple, similarPairs)函数的作用是将similarPairs列表中的每个子列表转换为元组,set去重
#{(a, b), (c, d)}
for i in range(len(sentence1)):
s1, s2 = sentence1[i], sentence2[i]
if s1 == s2 or (s1, s2) in sim or (s2, s1) in sim:
continue
return False
return True
3.4 单行键盘
题目描述
我们定制了一款特殊的键盘,所有的键都 排列在一行上 。
给定一个长度为 26
的字符串 keyboard
,来表示键盘的布局(索引从 0
到 25
)。一开始,你的手指在索引 0
处。要输入一个字符,你必须把你的手指移动到所需字符的索引处。手指从索引 i
移动到索引 j
所需要的时间是 |i - j|
您需要输入一个字符串 word
。写一个函数来计算用一个手指输入需要多少时间。
示例 1:
输入:keyboard = "abcdefghijklmnopqrstuvwxyz", word = "cba"
输出:4
解释:从 0 号键移动到 2 号键来输出 'c',又移动到 1 号键来输出 'b',接着移动到 0 号键来输出 'a'。
总用时 = 2 + 1 + 1 = 4.
示例 2:
输入:keyboard = "pqrstuvwxyzabcdefghijklmno", word = "leetcode"
输出:73
分思路析
首先用哈希表记录字符的下标,再遍历word加上差值即可
实现代码
class Solution:
def calculateTime(self, keyboard: str, word: str) -> int:
char_map = dict()
for i, char_s in enumerate(keyboard):
char_map[char_s] = i
cur_pos, step = 0, 0
for char_s in word:
step += abs(cur_pos - char_map[char_s])
cur_pos = char_map[char_s]
return step
3.5 移位字符串分组
题目描述
给定一个字符串,对该字符串可以进行 “移位” 的操作,也就是将字符串中每个字母都变为其在字母表中后续的字母,比如:"abc" -> "bcd"
。这样,我们可以持续进行 “移位” 操作,从而生成如下移位序列:
"abc" -> "bcd" -> ... -> "xyz"
给定一个包含仅小写字母字符串的列表,将该列表中所有满足 “移位” 操作规律的组合进行分组并返回。
示例:
输入:["abc", "bcd", "acef", "xyz", "az", "ba", "a", "z"]
输出:
[
["abc","bcd","xyz"],
["az","ba"],
["acef"],
["a","z"]
]
解释:可以认为字母表首尾相接,所以 'z' 的后续为 'a',所以 ["az","ba"] 也满足 “移位” 操作规律。
分思路析
对每个字符串进行“移位”操作,把他们都转换成以a开头的字符串,然后用哈希表进行分类
实现代码
class Solution:
def groupStrings(self, strings: List[str]) -> List[List[str]]:
mode_list = defaultdict(list)
for s in strings:
if s[0] == 'a':
mode_list[s].append(s)
else:
tmp = list(s) #转化成一种模式 从'a****'
for i in range(len(s)):
tmp[i] = chr( (ord(tmp[i]) - ord(s[0]) + 26) % 26 + ord('a') )
tmp = ''.join(tmp)
mode_list[tmp].append(s)
res = []
for mode, sublist in mode_list.items():
res.append(sublist)
return res
3.6 最大唯一数
题目描述
给你一个整数数组 A,请找出并返回在该数组中仅出现一次的最大整数。
如果不存在这个只出现一次的整数,则返回 -1。
示例 1:
输入:[5,7,3,9,4,9,8,3,1]
输出:8
解释:
数组中最大的整数是 9,但它在数组中重复出现了。而第二大的整数是 8,它只出现了一次,所以答案是 8。
示例 2:
输入:[9,9,8,8]
输出:-1
解释:
数组中不存在仅出现一次的整数。
分思路析
字典统计 注意不存在的情况
实现代码
class Solution:
def largestUniqueNumber(self, A: List[int]) -> int:
digit_freq = defaultdict(int)
for x in A:
digit_freq[x] += 1
rec = []
for digit, freq in digit_freq.items():
if freq == 1:
rec.append(digit)
if len(rec) == 0:
return -1
return max(rec)
3.7 数元素
题目描述
给你一个整数数组 arr
, 对于元素 x
,只有当 x + 1
也在数组 arr
里时,才能记为 1
个数。
如果数组 arr
里有重复的数,每个重复的数单独计算。
示例 1:
输入:arr = [1,2,3]
输出:2
解释:1 和 2 被计算次数因为 2 和 3 在数组 arr 里。
示例 2:
输入:arr = [1,1,3,3,5,5,7,7]
输出:0
解释:所有的数都不算, 因为数组里没有 2、4、6、8。
实现代码
class Solution:
def countElements(self, arr: List[int]) -> int:
##借助哈希字典统计,然后再遍历,查找
num_cnt = collections.Counter(arr)
res = 0
for num, cnt in num_cnt.items():
if (num + 1) in num_cnt:
res += cnt
return res
class Solution:
def countElements(self, arr: List[int]) -> int:
return sum(arr[i]+1 in set(arr) for i in range(len(arr)))
class Solution:
def countElements(self, arr: List[int]) -> int:
return sum(x + 1 in arr for x in arr)
3.8 找出所有行中最小公共元素
题目描述
给你一个 m x n
的矩阵 mat
,其中每一行的元素均符合 严格递增 。请返回 所有行中的 最小公共元素 。
如果矩阵中没有这样的公共元素,就请返回 -1
。
示例 1:
输入:mat = [[1,2,3,4,5],[2,4,5,8,10],[3,5,7,9,11],[1,3,5,7,9]]
输出:5
示例 2:
输入:mat = [[1,2,3],[2,3,4],[2,3,5]]
输出: 2
分思路析
有序必二分, 二分法
实现代码
二分查找
class Solution:
def smallestCommonElement(self, mat: List[List[int]]) -> int:
#if not mat:语句用于判断列表mat是否为空
if not mat:
return -1
# 二分查找 二分查找算法可以高效地在有序数组中查找目标元素
def binarySearch(nums, target):
left = 0
right = len(nums) - 1
#当左边界小于等于右边界时,进行循环。
while left <= right:
#计算中间元素的索引,使用整数除法以确保结果是整数
mid = (left + right) // 2 #向下取整
#如果中间元素就是目标元素,则返回 True
if nums[mid] == target:
return True
#如果中间元素小于目标元素,则在右半部分继续查找,将左边界更新为 mid + 1
elif nums[mid] < target:
left = mid + 1
#如果中间元素大于目标元素,则在左半部分继续查找,将右边界更新为 mid - 1
else:
right = mid - 1
return False
# 获取矩阵中的行数和列数
rows = len(mat)
cols = len(mat[0])
# 遍历第一行的元素
for num in mat[0]:
found = True # 标记当前元素是否在所有行中都存在
# 遍历除了第一行以外的每一行
for i in range(1, rows):
# 使用二分查找在当前行中找到当前元素
if not binarySearch(mat[i], num):
found = False
break
# 如果当前元素在所有行中都存在,则是最小公共元素
if found:
return num
# 没有找到最小公共元素
return -1
逐行对比最小值
class Solution:
def smallestCommonElement(self, mat: List[List[int]]) -> int:
if not mat:
return -1
n = len(mat)
m = len(mat[0])
counts = collections.defaultdict(int)
for i in range(m): # 列
for j in range(n): # 行
val = mat[j][i]
if counts[val] == n - 1:
return val
counts[val] += 1
return -1