python | collections模块中的Counter及应用

刷题时,Python 中的 Counter 类是可以快速统计元素的频率,帮助解决各种涉及计数的问题。Countercollections 模块中的一个类,本质上是一个字典,用于计数可哈希对象。

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 计数窗口中字符频率,并滑动窗口更新计数。

代码逻辑

  1. 初始化 Counter,统计窗口 [0, k) 内字符的频率
  2. 打印 Counter
  3. 开始滑动窗口
    • 右边加入 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)

  1. 初始化 defaultdict(list) 存储分组结果
  2. 遍历字符串列表 strs
    • 统计 s 中每个字符的频率(使用 Counter(s)
    • 按照 (字符, 频率) 进行排序并转换为 tuple(作为 dict 的 key)
    • 将字符串 s 存入 anagrams 的相应组中
  3. 返回 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 可以快速解决各种涉及计数的问题。它提供了高效的统计功能和丰富的内置方法,能够快速实现算法逻辑,减少代码量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值