刷题时,Python 中的 Counter
类是可以快速统计元素的频率,帮助解决各种涉及计数的问题。Counter
是 collections
模块中的一个类,本质上是一个字典,用于计数可哈希对象。
1. Counter 的基本用法
(1) 初始化
Counter
可以通过多种方式初始化:
- 从列表、元组或其他可迭代对象初始化:
from collections import Counter
# 从列表初始化
counter = Counter([1, 2, 2, 3, 3, 3])
print(counter) # 输出:Counter({3: 3, 2: 2, 1: 1})
- 从字符串初始化:
counter = Counter("gallahad")
print(counter) # 输出:Counter({'a': 3, 'l': 2, 'g': 1, 'h': 1, 'd': 1})
- 从字典初始化:
counter = Counter({'red': 4, 'blue': 2})
print(counter) # 输出:Counter({'red': 4, 'blue': 2})
(2) 常用方法
elements()
:返回一个迭代器,包含所有元素(重复元素会重复出现)。
counter = Counter("gallahad")
for element in counter.elements():
print(element, end=" ") # 输出:g a l l a h a d
most_common([n])
:返回出现次数最多的前 n 个元素及其计数。如果不指定 n,则返回所有元素。
counter = Counter("gallahad")
print(counter.most_common(2)) # 输出:[('a', 3), ('l', 2)]
update([iterable-or-mapping])
:增加计数。
counter = Counter("gallahad")
counter.update("hello")
print(counter) # 输出:Counter({'a': 3, 'l': 3, 'h': 2, 'e': 1, 'o': 1, 'g': 1, 'd': 1})
subtract([iterable-or-mapping])
:减少计数。
counter = Counter("gallahad")
counter.subtract("hello")
print(counter) # 输出:Counter({'a': 2, 'l': 1, 'g': 1, 'd': 1, 'h': -1, 'e': -1, 'o': -1})
2. Counter 在题目中的常见应用
(1) 字符串问题
- 统计字符频率:快速统计字符串中每个字符的出现次数。
from collections import Counter
s = "abracadabra"
counter = Counter(s)
print(counter) # 输出:Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
- 判断是否为变位词:两个字符串是否由相同字符组成。
def is_anagram(s1, s2):
return Counter(s1) == Counter(s2)
print(is_anagram("listen", "silent")) # 输出:True
print(is_anagram("hello", "world")) # 输出:False
(2) 数组问题
- 统计数字频率:快速统计数组中每个数字的出现次数。
from collections import Counter
nums = [1, 2, 2, 3, 3, 3]
counter = Counter(nums)
print(counter) # 输出:Counter({3: 3, 2: 2, 1: 1})
- 寻找多数元素:找出数组中出现次数超过一半的元素。
def majority_element(nums):
counter = Counter(nums)
return counter.most_common(1)[0][0]
print(majority_element([3, 2, 3])) # 输出:3
(3) 滑动窗口问题
- 统计窗口内字符的频率:在滑动窗口中快速统计字符的频率。
from collections import Counter
def count_characters(s, k):
counter = Counter()
for i in range(k):
counter[s[i]] += 1
print(counter) # 输出窗口 [0, k) 内的字符频率
for i in range(k, len(s)):
counter[s[i]] += 1
counter[s[i - k]] -= 1
if counter[s[i - k]] == 0:
del counter[s[i - k]]
print(counter) # 输出每个窗口的字符频率
count_characters("abcabcbb", 3)
使用
Counter
计数窗口中字符频率,并滑动窗口更新计数。
代码逻辑
- 初始化
Counter
,统计窗口[0, k)
内字符的频率 - 打印
Counter
- 开始滑动窗口
- 右边加入
s[i]
- 左边移除
s[i - k]
- 如果
s[i - k]
频率变成 0,则删除 - 打印
Counter
- 右边加入
输入
count_characters("abcabcbb", 3)
初始窗口 [0,3) = "abc"
Counter({'a': 1, 'b': 1, 'c': 1})
窗口 [1,4) = "bca"
- 加入
s[3] = 'a'
- 移除
s[0] = 'a'
Counter({'b': 1, 'c': 1, 'a': 1}) # 变化不大,因为 'a' 重新进入
窗口 [2,5) = "cab"
- 加入
s[4] = 'b'
- 移除
s[1] = 'b'
Counter({'c': 1, 'a': 1, 'b': 1})
窗口 [3,6) = "abc"
- 加入
s[5] = 'c'
- 移除
s[2] = 'c'
Counter({'a': 1, 'b': 1, 'c': 1})
窗口 [4,7) = "bcb"
- 加入
s[6] = 'b'
- 移除
s[3] = 'a'
Counter({'b': 2, 'c': 1})
窗口 [5,8) = "cbb"
- 加入
s[7] = 'b'
- 移除
s[4] = 'b'
Counter({'b': 2, 'c': 1})
最终输出
Counter({'a': 1, 'b': 1, 'c': 1})
Counter({'b': 1, 'c': 1, 'a': 1})
Counter({'c': 1, 'a': 1, 'b': 1})
Counter({'a': 1, 'b': 1, 'c': 1})
Counter({'b': 2, 'c': 1})
Counter({'b': 2, 'c': 1})
(4) 组合问题
- 判断是否可以组成特定字符串:判断一个字符串是否可以由另一个字符串的字符组成。
def can_form(s1, s2):
counter1 = Counter(s1)
counter2 = Counter(s2)
for char, count in counter1.items():
if counter2[char] < count:
return False
return True
print(can_form("abc", "aabbcc")) # 输出:True
print(can_form("abc", "ab")) # 输出:False
(5) 最频繁元素问题
- 找出出现次数最多的元素:快速找出数组或字符串中出现次数最多的元素。
from collections import Counter
s = "abracadabra"
counter = Counter(s)
most_common = counter.most_common(1)[0]
print(most_common) # 输出:('a', 5)
3. LeetCode例题
(1) LeetCode 347. 前 K 个高频元素
题目要求找出数组中出现次数最多的前 K 个元素。
from collections import Counter
import heapq
def topKFrequent(nums, k):
counter = Counter(nums)
return heapq.nlargest(k, counter.keys(), key=counter.get)
print(topKFrequent([1, 1, 1, 2, 2, 3], 2)) # 输出:[1, 2]
heapq.nlargest
函数从可迭代对象 iterable
中找出最大的 k
个元素,并返回一个列表。key
参数是一个函数,用于指定排序的依据。
在这个例子中:
k
是需要返回的最大元素的数量。counter.keys()
是可迭代对象,包含所有元素。key=counter.get
表示按照每个元素在Counter
中的出现次数进行排序。
最终,heapq.nlargest(k, counter.keys(), key=counter.get)
会返回出现次数最多的前 k
个元素。
(2) LeetCode 49. 字母异位词分组
题目要求将字符串数组中的字母异位词分组。
from collections import Counter, defaultdict
def groupAnagrams(strs):
anagrams = defaultdict(list)
for s in strs:
count = tuple(sorted(Counter(s).items()))
anagrams[count].append(s)
return list(anagrams.values())
print(groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"]))
# 输出:[['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]
使用 哈希表(defaultdict)+ 计数法(Counter) 来分组字母异位词(Anagrams)
- 初始化
defaultdict(list)
存储分组结果 - 遍历字符串列表
strs
- 统计
s
中每个字符的频率(使用Counter(s)
) - 按照
(字符, 频率)
进行排序并转换为tuple
(作为dict
的 key) - 将字符串
s
存入anagrams
的相应组中
- 统计
- 返回
anagrams.values()
转换为列表
groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"])
单词 | Counter(s) | 排序后的 tuple | 分组结果 |
---|---|---|---|
"eat" | {'e': 1, 'a': 1, 't': 1} | (('a', 1), ('e', 1), ('t', 1)) | [['eat']] |
"tea" | {'t': 1, 'e': 1, 'a': 1} | (('a', 1), ('e', 1), ('t', 1)) | [['eat', 'tea']] |
"tan" | {'t': 1, 'a': 1, 'n': 1} | (('a', 1), ('n', 1), ('t', 1)) | [['eat', 'tea'], ['tan']] |
"ate" | {'a': 1, 't': 1, 'e': 1} | (('a', 1), ('e', 1), ('t', 1)) | [['eat', 'tea', 'ate'], ['tan']] |
"nat" | {'n': 1, 'a': 1, 't': 1} | (('a', 1), ('n', 1), ('t', 1)) | [['eat', 'tea', 'ate'], ['tan', 'nat']] |
"bat" | {'b': 1, 'a': 1, 't': 1} | (('a', 1), ('b', 1), ('t', 1)) | [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']] |
最终 anagrams
的值:
[['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]
4. 总结
Counter
可以快速解决各种涉及计数的问题。它提供了高效的统计功能和丰富的内置方法,能够快速实现算法逻辑,减少代码量。