1. 有效的字母异位词
1.1 哈希表法
判断两个字符串是否由同样的字母组成, 考虑用哈希表, 因为字母一共只有26个, 哈希值小且范围可控, 所以考虑用数组, a-z小写字母的ASCII码是连续的
ord()函数是一个python中的内置函数, 用来返回指定字符的Unicode码, 也就是字符在Unicode编码表中的数值表示.
为了避免麻烦, 直接用数组中的字符i的ord()减去ord最开始的a, 得到字符在record中的索引(这里注意遍历的是字母本身而不是索引, 也要注意ord()并非ord[])
用这种方式记录每个字母在字符串中出现的次数, 其中每个索引对应一个字母, 如果出现了一次就加1, 在另一个字符串中出现一次就减1, 最后检查得到的数组元素是否都为0
record = [0] * 26
if len(s) != len(t):
return False # 如果两字符串长度不同,直接返回False
for i in s:
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:
return False
return True
1.2 counter法
1.2.1 counter类简介
Counter
是 Python 标准库 collections
模块中的一个类,用于对数据中的元素进行计数。它可以接收多种形式的输入,如列表、字符串等,并返回一个字典,其中的键是输入数据中的唯一元素,值是这些元素在输入数据中出现的次数. 例如
from collections import Counter
a_count = Counter(s)
s
可以是任何可迭代的数据类型,例如字符串、列表或元组。Counter(s)
会创建一个计数器对象,其中包含了s
中每个元素的出现次数。a_count
将是一个字典类型的对象,其中包含了从s
中提取的元素及其对应的计数。
most_common()
还可以返回数据中出现次数最多的元素及其计数。
1.2.2 counter法解题
就是看两个字符串生成的字典是否完全一样
用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
1.3 defaultdict法
1.3.1 defaultdict介绍
defaultdict是创建字典的方法, 是python标准库collection模块中的一种特殊类型的字典, 特点是提供一个默认值, 当字典被访问时, 如果键不存在, 则返回一个默认值而不是抛出“Keyerror”
在 Python 的 collections
模块中使用 defaultdict
时,需要提供一个无参数的函数或类型来作为默认值的工厂。
defaultdict(0)是错误的用法. 括号里面必须是一个可调用的对象.
在这里,int
是 Python 中的标准整数类型。当作为 defaultdict
的参数时,int
用作默认工厂函数,这意味着当字典在尝试访问一个不存在的键时,会自动调用 int()
来产生一个默认值。由于调用 int()
返回 0
,因此当访问一个不存在的键时,defaultdict(int)
会自动为这个键赋值 0
。
也可以这样写d = defaultdict(lambda: 0), 还有一种查找key时的写法, 比如 my_dict.get('key', 0), 如果字典中找不到key这个键, 就返回0
1.3.2 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
2. 两个数组的交集(349)
2.1.用字典
用一个字典记录下第一个数组的内容, key是数组里面的数值, value是出现次数, 用get 如果之前没有过这个数值, 则返回0, 之后再加1, 如果之前有过, 那么就直接加1. 这样就可以统计数组中每个数值出现过的次数
然后遍历nums2里面的数, 看能不能作为key出现在字典中, 如果可以如此, 就把这个数值添加到res中, res就是重复出现过的数
从字典中删除已经处理过的项可以减少未来查找或操作的成本。尤其是当 table
很大时,删除不再需要的键可以帮助降低内存占用和可能的查找时间。我们只需要处理table和num2相匹配的第一个相应的数字, 防止再次匹配同一个数字
table = {}
res = set()
for i in nums1:
table[i] = table.get(i, 0) + 1
for i in nums2:
if i in table:
res.add(i)
del table[num]
return list(res)
2.2 用数组
这里要注意的主要是count中是用索引来记录num中的值的, num中的值由num的长度决定, 而count的值对应的就是数字出现的次数
另外要注意的是, 判断有交集的条件是出现次数的成绩大于 0
count1 = [0]*1001
count2 = [0]*1001
res = []
for i in range(len(nums1)):
count1[nums1[i]] += 1
for i in range(len(nums2)):
count2[nums2[i]] += 1
for i in range(1001):
if count1[i] * count2[i] != 0:
res.append(i)
return res
2.3 用集合
直接用set取交集
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
3. 快乐数(202)
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
3.1 求各个位数平方和的方法
3.1.1 数学计算
python中有divmod函数
n, r = divmod(n, 10)
这行代码的意思是把n除以10的商赋予给n, 将余数赋予给r
也就是说如果n=123, n更新为12, r更新为3, 第二次循环中n更新为1, 这样计算r的平方和也就是计算每一位的平方和
类似的写法有
while n:
new_sum += (n%10) ** 2
n = n//10
3.1.2 把数字转化为字符串
new_num = 0
n_str = str(n)
for i in n_str:
new_num+=int(i)**2
3.2 判断是否重复出现的方法
题目中说了会 无限循环,那么也就是说求和的过程中,sum会重复出现,这对解题很重要!
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。
3.2.1 集合法
class Solution(object):
def isHappy(self, n):
"""
:type n: int
:rtype: 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):
new_sum = 0
while n:
n, r = divmod(n, 10)
new_sum += r ** 2
return new_sum
3.2.2. 数组法
数组法其实就是用数组记录是否会循环(重复), 和集合法差别不大
record = []
while n not in record:
record.append(n)
3.2.3 双指针法
因为如果不是快乐数, 就会出现循环, 所以用fast和slow两个指针跟踪sum的更新情况, fast一次算两个sum, slow每次算一个sum, 如果二者相等, 则说明出现循环(因为fast和slow速度相差1, 所以如果有循环一定会相等)
举例 4: 4, 16, 37, 58, 89, 145, 42, 20, 4
fast: 37, 89, 42, 4, 37, 89, 37, 89, 42, 4, 37
slow:4, 16, 37, 58, 89, 145, 42, 20, 4, 16, 37
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
4. 两数之和
这道题的核心其实是想到找到另一个符合条件的数是否被遍历过, 如target=9, 遍历到的是6, 就找3有没有之前被遍历过, 如果有就返回下标
注意两和问题只需要找出其中任意两个其和为target就可以
4.1 字典法
record = {}
index = 0
for i in nums:
if target - i in record:
return [record[target-i], index]
else:
record[i] = index
index += 1
return []
其中字典法还可以用一个内置函数简化 for index, value in enumerate(nums):, 这行代码用来遍历列表或者其他可迭代对象, 同时获取每个元素的索引和值
- 例如,如果
nums = [10, 20, 30]
,enumerate(nums)
会生成(0, 10)
、(1, 20)
、(2, 30)
。
index, value是元组解包的方式, 将元组氛围index索引和value对应元素值这两部分
找不到target-value, 就把这个写入字典中即可
for index, value in enumerate(nums):
if target - value in records: # 遍历当前元素,并在map中寻找是否有匹配的key
return [records[target- value], index]
records[value] = index # 如果没找到匹配对,就把访问过的元素和下标加入到map中
return []
4.2 集合法
其实就是用集合存入元素, 然后如果集合中有需要的元素, 就用nums.index()返回在数组中的索引值, 注意nums.index()如果是数组中不存在元素, 会丢出ValueError的异常
def twoSum(self, nums: List[int], target: int) -> List[int]:
#创建一个集合来存储我们目前看到的数字
seen = set()
for i, num in enumerate(nums):
complement = target - num
if complement in seen:
return [nums.index(complement), i]
seen.add(num)
4.3 暴力法
两个for循环, 挨个相加对比, 复杂度应该是O(n**2)
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[i] + nums[j] == target:
return [i,j]
4.4 双指针法
首先用sorted(nums)对数组进行排序, 再将两个指针left和right分别放在数组最左边和最右边
直到两个指针相遇之前都一直循环, 在循环内部计算left和right指向元素的和, 记为current_sum
如果cur_sum等于target, 就找到两个元素在原始数组nums中的索引, 这里要考虑一个特殊情况, 就是两个元素相等时, 用index会返回这个元素第一次出现的位置的索引
所以要找第二次出现位置的索引, 从索引第一次出现的位置+1到数组末尾的部分, 就跳过课第1次找到的索引, 但是注意这里找到的索引是切割以后第一次出现位置下一个数作为起点得到的索引, 为了还原成在原数组的索引, 要加上第一次出现位置的索引和1
if left_index == right_index:
right_index = nums[left_index+1:].index(nums_sorted[right]) + left_index + 1
如果cur_sum小于target就把left向右移动, 增加和的值
如果cur_sum大于target就把right向左移动, 减少和的值
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 对输入列表进行排序
nums_sorted = sorted(nums)
# 使用双指针
left = 0
right = len(nums_sorted) - 1
while left < right:
current_sum = nums_sorted[left] + nums_sorted[right]
if current_sum == target:
# 如果和等于目标数,则返回两个数的下标
left_index = nums.index(nums_sorted[left])
right_index = nums.index(nums_sorted[right])
if left_index == right_index:
right_index = nums[left_index+1:].index(nums_sorted[right]) + left_index + 1
return [left_index, right_index]
elif current_sum < target:
# 如果总和小于目标,则将左侧指针向右移动
left += 1
else:
# 如果总和大于目标值,则将右指针向左移动
right -= 1