《代码随想录》学习笔记,原链接:https://programmercarl.com/
哈希表理论基础
哈希表(Hash Table),用来快速判断某个元素是否在集合中出现过(当题目中出现需要判断满足某个条件的某个元素/某些元素是否出现过的场景,第一时间想到哈希法),即适用于与“查询”相关的操作。
哈希表采用的数据结构包括以下三类:
- 数组:在哈希值和哈希表的大小范围都比较小的时候使用(优先使用,数组是最直接、运算速度最快的)。
- 集合set:在哈希值和哈希表的大小范围都比较大的时候使用。
- 匹配map:如果每个key都需要保存其对应的value时使用。
但是,哈希表也是牺牲时间换取空间的方法,因为要使用额外的数组、set或者map来存放数据,才能实现快速查找。
242.有效的字母异位词(使用数组哈希表)
- 核心代码模式
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0] * 26 # 记录字符串26个字母分别出现的次数
for letter in s:
record[ord(letter) - ord("a")] += 1 # 统计字符串s中每个字母出现的次数,ord()表示取字符的ASCII码
for letter in t:
record[ord(letter) - ord("a")] -= 1 # 统计字符串s中每个字母出现的次数,ord()表示取字符的ASCII码
for i in range(26): # 判断s和t中每个字母出现的次数是否相同
if record[i] != 0:
return False
return True
- ACM模式
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0] * 26 # 记录字符串26个字母分别出现的次数
for letter in s:
record[ord(letter) - ord("a")] += 1 # 统计字符串s中每个字母出现的次数,ord()表示取字符的ASCII码
for letter in t:
record[ord(letter) - ord("a")] -= 1 # 统计字符串s中每个字母出现的次数,ord()表示取字符的ASCII码
for i in range(26): # 判断s和t中每个字母出现的次数是否相同
if record[i] != 0:
return False
return True
# 输入字符串s和t
s = input("输入小写字母字符串s:")
t = input("输入小写字母字符串t:")
# 有效的字母异位词
solution = Solution()
result = solution.isAnagram(s, t)
print(result)
383.赎金信
- 核心代码模式
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
record = [0] * 26 # 构建哈希表数组,记录26个字母分别出现的次数
# 统计magazine中各个字母出现的次数
for letter in magazine:
record[ord(letter) - ord("a")] += 1
# 统计ransomNote中每个字母出现的次数
for letter in ransomNote:
record[ord(letter) - ord("a")] -= 1
for i in range(26): # 判断ransomNote能不能由magazine里面的字符构成
if record[i] < 0: # ransomNote中有元素不能在magazine中找到
return False
return True
- ACM模式
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
record = [0] * 26 # 构建哈希表数组,记录26个字母分别出现的次数
# 统计magazine中各个字母出现的次数
for letter in magazine:
record[ord(letter) - ord("a")] += 1
# 统计ransomNote中每个字母出现的次数
for letter in ransomNote:
record[ord(letter) - ord("a")] -= 1
for i in range(26): # 判断ransomNote能不能由magazine里面的字符构成
if record[i] < 0: # ransomNote中有元素不能在magazine中找到
return False
return True
# 输入字符串s和t
magazine = input("输入小写字母字符串magazine :")
ransomNote = input("输入小写字母字符串ransomNote:")
# 赎金信
solution = Solution()
result = solution.canConstruct(ransomNote, magazine)
print(result)
49.字母异位词分组
- 核心代码模式
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
record = {}
for string in strs:
key = ''.join(sorted(string)) # sorted函数返回的是一个已排序的字母列表,而不是一个字符串,需要使用join()函数转换回字符串
# 将排序后的字符串作为key,原字符串构成的列表作为value
if key not in record:
record[key] = [string] # 注意,需要将string初始化成列表元素
else:
record[key].append(string)
result = list(record.values()) # 字典的values()方法返回一个dict_values类型的对象,包含字典中所有值,但是该对象需要使用list()函数转换为列表。
return result
【注1】key = ''.join(sorted(string)) # sorted函数返回的是一个已排序的字母列表,而不是一个字符串,需要使用join()函数转换回字符串。
【注2】 result = list(record.values()) # 字典的values()方法返回一个dict_values类型的对象,包含字典中所有值,但是该对象需要使用list()函数转换为列表。
- ACM模式
class Solution:
def groupAnagrams(self, strs):
record = {}
for string in strs:
key = ''.join(sorted(string)) # sorted函数返回的是一个已排序的字母列表,而不是一个字符串,需要使用join()函数转换回字符串
# 将排序后的字符串作为key,原字符串构成的列表作为value
if key not in record:
record[key] = [string] # 注意,需要将string初始化成列表元素
else:
record[key].append(string)
result = list(record.values()) # 字典的values()方法返回一个dict_values类型的对象,包含字典中所有值,但是该对象需要使用list()函数转换为列表。
return result
strs = []
# 输入字符串列表
while True:
string = input("输入字符串元素(按回车结束输入):")
if string == "":
break
strs.append(string)
# 字母异位词分组
solution = Solution()
result = solution.groupAnagrams(strs)
print(result)
438.找到字符串中所有字母异位词
- 核心代码模式
from collections import Counter
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
result = []
lenth = len(p)
p_count = Counter(p)
s_count = Counter(s[: lenth - 1])
for i in range(lenth - 1, len(s)):
s_count[s[i]] += 1
if s_count == p_count:
result.append(i - lenth + 1)
s_count[s[i - lenth + 1]] -= 1
if s_count[s[i - lenth + 1]] == 0:
del s_count[s[i - lenth + 1]]
return result
- ACM模式
from collections import Counter
class Solution:
def findAnagrams(self, s, p):
result = []
lenth = len(p)
p_count = Counter(p)
s_count = Counter(s[: lenth - 1])
for i in range(lenth - 1, len(s)):
s_count[s[i]] += 1
if s_count == p_count:
result.append(i - lenth + 1)
s_count[s[i - lenth + 1]] -= 1
if s_count[s[i - lenth + 1]] == 0:
del s_count[s[i - lenth + 1]]
return result
strs = []
# 输入字符串p和s
s = input("输入字符串s:")
p = input("输入字符串p:")
# 找到字符串中所有字母异位词
solution = Solution()
result = solution.findAnagrams(s, p)
print(result)
349.两个数组的交集 (使用集合set哈希表)
由于题目要求输出结果中的每个元素是唯一的,因此需要对于结果进行去重操作。而由于哈希值没有做限制,其数字范围很大,不适合使用数组来作为哈希表,因此使用集合set来作为哈希表。
本题逻辑如下:
- 核心代码模式
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
record = set() # set哈希表,存放nums1中的去重元素
result = set() # 存放结果
for num in nums1: # 记录nums1中出现的数字
if num not in record:
record.add(num)
for num in nums2: # 寻找两个数组的交集
if num in record:
result.add(num)
record.remove(num)
return list(result) # 注意返回类型提示为“list”类型
# 可以直接使用下面这一行代码,即可得到结果
# return list(set(nums1).intersection(set(nums2)))
【注1】哈希表选择数组和集合的情况分析:
- 如果没有限制数值的大小,而且哈希表的值比较少,比较分散,跨度非常大,则使用数组就会造成空间的极大浪费。
- 但是,set会把数值映射到key上都要进行哈希运算,直接使用set相对于数组,不仅占用空间大,而且速度比数组更慢。
因此,遇到问题,不能无脑全部使用set。
【注2】关于Python中的集合set的一些知识记录:
- ACM模式
class Solution:
def intersection(self, nums1, nums2):
record = set() # set哈希表,存放nums1中的去重元素
result = set() # 存放结果
for num in nums1: # 记录nums1中出现的数字
if num not in record:
record.add(num)
for num in nums2: # 寻找两个数组的交集
if num in record:
result.add(num)
record.remove(num)
return list(result) # 注意返回类型提示为“list”类型
# 可以直接使用下面这一行代码,即可得到结果
# return list(set(nums1).intersection(set(nums2)))
# 输入正整数数组nums1和nums2
nums1 = list(map(int, input("输入整数数组nums1:").split(",")))
nums2 = list(map(int, input("输入整数数组nums2:").split(",")))
# 两个数组的交集
solution = Solution()
result = solution.intersection(nums1, nums2)
print(result)
350.两个数组的交集II
- 核心代码模式
class Solution:
def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
record = {} # 创建空字典哈希表
result = [] # 记录交集结果
for num in nums1: # 统计nums1中的字母个数
# num = str(num) # 这句可加可不加
if num not in record:
record[num] = 0
record[num] += 1
for num in nums2: # 寻找两个数组的交集
# num = str(num) # 这句可加可不加
if num in record and record[num] > 0: # 首先需要判断num是否在record中
result.append(int(num))
record[num] -= 1
return result
【注】关于Python中的集合set的一些知识记录:
- ACM模式
class Solution:
def intersect(self, nums1, nums2):
record = {} # 创建空字典哈希表
result = [] # 记录交集结果
for num in nums1: # 统计nums1中的字母个数
# num = str(num) # 这句可加可不加
if num not in record:
record[num] = 0
record[num] += 1
for num in nums2: # 寻找两个数组的交集
# num = str(num) # 这句可加可不加
if num in record and record[num] > 0: # 首先需要判断num是否在record中
result.append(int(num))
record[num] -= 1
return result
# 输入正整数数组nums1和nums2
nums1 = list(map(int, input("输入整数数组nums1:").split(",")))
nums2 = list(map(int, input("输入整数数组nums2:").split(",")))
# 两个数组的交集II
solution = Solution()
result = solution.intersect(nums1, nums2)
print(result)
202.快乐数
- 核心代码模式
class Solution:
def isHappy(self, n: int) -> bool:
result = []
summation = n
while True:
nums = list(map(int, str(summation))) # 将summation按照每一位,拆分成整数列表
summation = 0 # 每次都要对summation重新赋值初始化
for i in nums: # 计算当前每个位置上的数字的平方和
summation += (i ** 2)
# 如果无限循环,说明肯定在某个时刻,summation出现了循环,则返回False
if summation in result:
return False
# 如果summation的结果为1,说明是快乐数
if summation == 1:
return True
result.append(summation) # 统计已经出现过的平方和
【注】 关于“list(map(int, str(summation)))”的分析:
summation = 12345
print(summation, type(summation)) # 输出12345 <class 'int'>
result1 = str(summation)
print(result1, type(result1)) # 输出12345 <class 'str'>
# map()函数用于将一个函数,应用到某个可迭代对象的所有元素上,并返回一个新的可迭代对象。
result2 = map(int, str(summation))
print(result2, type(result2)) # 输出<map object at 0x0000016420FAC6C8> <class 'map'>
# 使用for循环访问这个新的可迭代对象的元素值
for i in result2:
# 1 <class 'int'>
# 2 <class 'int'>
# 3 <class 'int'>
# 4 <class 'int'>
# 5 <class 'int'>
print(i, type(i))
result3 = list(map(int, str(summation)))
print(result3, type(result3)) # [1, 2, 3, 4, 5] <class 'list'>
- ACM模式
class Solution:
def isHappy(self, n):
result = []
summation = n
while True:
nums = list(map(int, str(summation))) # 将summation按照每一位,拆分成整数列表
summation = 0 # 每次都要对summation重新赋值初始化
for i in nums: # 计算当前每个位置上的数字的平方和
summation += (i ** 2)
# 如果无限循环,说明肯定在某个时刻,summation出现了循环,则返回False
if summation in result:
return False
# 如果summation的结果为1,说明是快乐数
if summation == 1:
return True
result.append(summation) # 统计已经出现过的平方和
# 输入n
n = int(input("输入n:"))
# 快乐数
solution = Solution()
result = solution.isHappy(n)
print(result)
1.两数之和
- 核心代码模式
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
record = {} # 使用字典哈希表,记录已经访问的元素
for i in range(len(nums)):
if target - nums[i] not in record: # 将已经访问过的元素记录到字典里
record[nums[i]] = i
else:
return [record[target - nums[i]], i] # 记录满足条件的两个数字的下标
【注】关于本题的几个问题:
- ACM模式
class Solution:
def twoSum(self, nums, target):
record = {} # 使用字典哈希表,记录已经访问的元素
for i in range(len(nums)):
if target - nums[i] not in record: # 将已经访问过的元素记录到字典里
record[nums[i]] = i
else:
return [record[target - nums[i]], i] # 记录满足条件的两个数字的下标
# 输入整数列表nums和目标和target
nums = list(map(int, input("nums:").split(",")))
target = int(input("target:"))
# 两数之和
solution = Solution()
result = solution.twoSum(nums, target)
print(result)
454.四数相加II
- 核心代码模式
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
record = {}
count = 0
# 计算数组nums1和nums2中的两个元素之和,并将元素和以及出现次数保存到record
for i in range(len(nums1)):
for j in range(len(nums2)):
temp_sum = nums1[i] + nums2[j]
if temp_sum not in record:
record[temp_sum] = 0
record[temp_sum] += 1
# 判断nums3和nums4中是否满足四数之和等于0
for k in range(len(nums3)):
for l in range(len(nums4)):
temp_sum = nums3[k] + nums4[l]
if 0 - temp_sum in record:
count += record[0 - temp_sum] # 此处注意不能写count += 1
# 返回满足四数之和等于0的组合个数
return count
- ACM模式
class Solution:
def fourSumCount(self, nums1, nums2, nums3, nums4):
record = {}
count = 0
# 计算数组nums1和nums2中的两个元素之和,并将元素和以及出现次数保存到record
for i in range(len(nums1)):
for j in range(len(nums2)):
temp_sum = nums1[i] + nums2[j]
if temp_sum not in record:
record[temp_sum] = 0
record[temp_sum] += 1
# 判断nums3和nums4中是否满足四数之和等于0
for k in range(len(nums3)):
for l in range(len(nums4)):
temp_sum = nums3[k] + nums4[l]
if 0 - temp_sum in record:
count += record[0 - temp_sum] # 此处注意不能写count += 1
# 返回满足四数之和等于0的组合个数
return count
# 输入整数列表nums
nums1 = list(map(int, input("nums1:").split(",")))
nums2 = list(map(int, input("nums2:").split(",")))
nums3 = list(map(int, input("nums3:").split(",")))
nums4 = list(map(int, input("nums4:").split(",")))
# 四数之和II
solution = Solution()
result = solution.fourSumCount(nums1, nums2, nums3, nums4)
print(result)
15.三数之和
- 核心代码模式
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort() # 对原数组进行升序排序
result = [] # 统计满足条件的三元组
for i in range(len(nums) - 2):
if nums[i] > 0: # 升序数组中,i为最小值,如果i>0,则三数之和一定大于0
break
if i > 0 and nums[i] == nums[i - 1]: # i去重
continue
j, k = i + 1, len(nums) - 1 # 三指针法
while j < k: # 不能相等,否则只有两个数
summation = nums[i] + nums[j] + nums[k] # 计算三数之和,不能将这一步放出while循环
if summation < 0:
j += 1 # 增大summation
elif summation > 0:
k -= 1 # 减小summation
else:
result.append([nums[i], nums[j], nums[k]]) # 记录这个三元组
j += 1
k -= 1
while j < k and nums[j] == nums[j - 1]: # j去重,注意要保证j < k
j += 1
while j < k and nums[k] == nums[k + 1]: # k去重,注意要保证j < k
k -= 1
return result # 返回结果三元组
【注】 此题如果向上面一样使用哈希法,需要套两层for循环。虽然也可以运行通过不会超时,但是这种方法很难进行剪枝和去重操作。而且,本题只需要返回元素值,而不需要返回下标,因此,使用先将数组进行排序,之后使用双指针法。方法原理图如下(具体逻辑见代码):
该方法在能够实现功能的前提下,方便进行去重和剪枝操作。
- ACM模式
class Solution:
def threeSum(self, nums):
nums.sort() # 对原数组进行升序排序
result = [] # 统计满足条件的三元组
for i in range(len(nums) - 2):
if nums[i] > 0: # 升序数组中,i为最小值,如果i>0,则三数之和一定大于0
break
if i > 0 and nums[i] == nums[i - 1]: # i去重
continue
j, k = i + 1, len(nums) - 1 # 三指针法
while j < k: # 不能相等,否则只有两个数
summation = nums[i] + nums[j] + nums[k] # 计算三数之和,不能将这一步放出while循环
if summation < 0:
j += 1 # 增大summation
elif summation > 0:
k -= 1 # 减小summation
else:
result.append([nums[i], nums[j], nums[k]]) # 记录这个三元组
j += 1
k -= 1
while j < k and nums[j] == nums[j - 1]: # j去重,注意要保证j < k
j += 1
while j < k and nums[k] == nums[k + 1]: # k去重,注意要保证j < k
k -= 1
return result # 返回结果三元组
# 输入整数列表nums
nums = list(map(int, input("nums:").split(",")))
# 三数之和
solution = Solution()
result = solution.threeSum(nums)
print(result)
18.四数之和
- 核心代码模式
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums = sorted(nums) # 排序
result = []
for a in range(len(nums)):
if target > 0 and nums[a] > target: # 剪枝
break
if a > 0 and nums[a] == nums[a - 1]: # 去重,注意一定要加a > 0判断
continue
for b in range(a + 1, len(nums)):
if target > 0 and nums[a] + nums[b] > target: # 剪枝
break
if b > a + 1 and nums[b] == nums[b - 1]: # 去重,注意一定要加b > a + 1判断
continue
slow, fast = b + 1, len(nums) - 1 # 双指针
while slow < fast:
summation = nums[a] + nums[b] + nums[slow] + nums[fast] # 求和
if summation < target:
slow += 1
elif summation > target:
fast -= 1
else:
result.append([nums[a], nums[b], nums[slow], nums[fast]]) # 保存结果
slow += 1
fast -= 1
while slow < fast and nums[slow] == nums[slow - 1]: # 去重,一定要加slow < fast判断
slow += 1
while slow < fast and nums[fast] == nums[fast + 1]: # 去重,一定要加slow < fast判断
fast -= 1
return result
【注1】对于a和b在进行去重操作时,一定要进行“当前位置不是初始化的位置”的判断,否则如图下所示,会在初始化的时候,直接进行 += 1的操作,从而使得结果为空。
【注2】对于c和d进行去重操作时,一定要进行“slow < fast”判断,否则可能会超出列表范围。
- ACM模式
class Solution:
def fourSum(self, nums, target):
nums = sorted(nums) # 排序
result = []
for a in range(len(nums)):
if target > 0 and nums[a] > target: # 剪枝
break
if a > 0 and nums[a] == nums[a - 1]: # 去重,注意一定要加a > 0判断
continue
for b in range(a + 1, len(nums)):
if target > 0 and nums[a] + nums[b] > target: # 剪枝
break
if b > a + 1 and nums[b] == nums[b - 1]: # 去重,注意一定要加b > a + 1判断
continue
slow, fast = b + 1, len(nums) - 1 # 双指针
while slow < fast:
summation = nums[a] + nums[b] + nums[slow] + nums[fast] # 求和
if summation < target:
slow += 1
elif summation > target:
fast -= 1
else:
result.append([nums[a], nums[b], nums[slow], nums[fast]]) # 保存结果
slow += 1
fast -= 1
while slow < fast and nums[slow] == nums[slow - 1]: # 去重,一定要加slow < fast判断
slow += 1
while slow < fast and nums[fast] == nums[fast + 1]: # 去重,一定要加slow < fast判断
fast -= 1
return result
# 输入整数列表nums和目标和target
nums = list(map(int, input("nums:").split(",")))
target = int(input("target:"))
# 四数之和
solution = Solution()
result = solution.fourSum(nums, target)
print(result)