1170.比较字符串最小字母的出现频率
解法1:
python
class Solution:
def f(self, s: str) -> int:
cnt = 0
ch = 'z'
for c in s:
if c < ch:
ch = c
cnt = 1
elif c == ch:
cnt += 1
return cnt
def numSmallerByFrequency(self, queries: List[str], words: List[str]) -> List[int]:
count = [0] * 12
for s in words:
count[self.f(s)] += 1
for i in range(9, 0, -1):
count[i] += count[i + 1]
res = []
for s in queries:
res.append(count[self.f(s) + 1])
return res
作者:力扣官方题解
链接:https://leetcode.cn/problems/compare-strings-by-frequency-of-the-smallest-character/solutions/2297291/bi-jiao-zi-fu-chuan-zui-xiao-zi-mu-chu-x-pb50/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解法2:排序+查找
python
class Solution:
def f(self, s: str):
# 如何实现f?统计最小字母的出现频次!
cnt = 0 # 最小字母出现次数
ch = 'z' # 初始化当前的最小字母
# 遍历s所有字符c,只要出现更小的就更新cnt为1
for c in s:
if c < ch: # 当前字符c,小于ch
ch = c # 更新ch
cnt = 1 # 更新计数器
elif c == ch:
cnt += 1
return cnt
def lowerbound(self, nums, target):
left = 0
right = len(nums)-1 # 闭区间
while left <= right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return left
def numSmallerByFrequency(self, queries: List[str], words: List[str]) -> List[int]:
# 对于每个queries[i],统计words中有多少个word满足 f(queries[i]) < f(word) , f(xxx)是最小字母出现次数
# 已知所有字符长度最长10
# 用一个count[]
res = []
# 计算所有f(word),存到对应位置
count_words = [0] * len(words)
for i in range(len(words)): # self.f(s)可以计算words中每个word的最小字母出现频率
count_words[i] = self.f(words[i])
# 接下来比对所有querie,看每个querie
count_words.sort()
queries_words = [0] * len(queries)
for i in range(len(queries)): # self.f(s)可以计算words中每个word的最小字母出现频率
queries_words[i] = self.f(queries[i])
x = self.lowerbound(count_words, queries_words[i]+1)
x = len(count_words) - x
res.append(x)
return res
解法3:
python
class Solution:
def numSmallerByFrequency(self, queries: List[str], words: List[str]) -> List[int]:
a = sorted(s.count(min(s)) for s in words)
return [len(a) - bisect_right(a, q.count(min(q))) for q in queries]
以下是对这三种方法思路、异同以及核心思路的详细解释:
一、整体问题分析
题目要求对于给定的两个字符串数组 queries 和 words,定义函数 f(s) 来统计字符串 s 中字典序最小字母的出现频次,然后针对每个 queries[i],统计 words 中满足 f(queries[i]) < f(word) 的词的数目。
二、三种方法详解
1. 第一种方法(官方题解)
- 核心思路 :
- 首先实现函数
f(s),通过遍历字符串s,不断更新当前遇到的字典序最小字母ch以及它的出现频次cnt。 - 然后提前计算出所有
words中字符串的f(s)值,并使用一个长度固定为12的整数数组count(因为f(s)的范围是[1, 10])来统计每种f(words[i])的个数。 - 为了加快后续查询答案的计算速度,通过倒序遍历
count数组,将其更新为后缀和数组。这样,对于每个queries[i],对应的答案就是count[f(queries[i]) + 1]。
- 首先实现函数
- 具体步骤 :
- 在
f(self, s: str) -> int函数中:- 初始化
cnt = 0表示最小字母出现次数,ch = 'z'作为当前遇到的字典序最小的字母。 - 遍历字符串
s中的每个字符c:- 如果
c < ch,说明遇到了更小的字典序字母,更新ch为c,并将cnt重置为1。 - 如果
c == ch,说明是当前最小字母再次出现,将cnt加1。
- 如果
- 最后返回
cnt,即为字符串s中字典序最小字母的出现频次。
- 初始化
- 在
numSmallerByFrequency函数中:- 初始化
count = [0] * 12,用于统计每种f(words[i])的个数。 - 遍历
words数组,通过count[self.f(s)] += 1计算每个words[i]的f(s)值并在count数组中相应位置累加计数。 - 通过倒序遍历
count数组,将其更新为后缀和数组,即for i in range(9, 0, -1): count[i] += count[i + 1];。 - 最后遍历
queries数组,对于每个queries[i],通过res.append(count[self.f(s) + 1])计算满足条件的words中字符串的个数并添加到结果数组res中。
- 初始化
- 在
2. 第二种方法(你的解法)
- 核心思路 :
- 同样先实现函数
f(s)来计算字符串中字典序最小字母的出现频次。 - 然后分别计算出
words数组中每个字符串的f(s)值存到count_words数组,以及queries数组中每个字符串的f(s)值存到queries_words数组。 - 对
count_words数组进行排序,再针对每个queries_words[i],通过二分查找函数lowerbound在count_words数组中找到大于queries_words[i] + 1的最小索引x,最后通过len(count_words) - x得到满足条件的words中字符串的个数并添加到结果数组res中。
- 同样先实现函数
- 具体步骤 :
- 在
f(self, s: str)函数中,实现逻辑与第一种方法的f(s)函数基本相同,通过遍历字符串s,更新最小字母及其出现频次,最后返回频次值。 - 在
numSmallerByFrequency函数中:- 初始化
count_words = [0] * len(words)和queries_words = [0] * len(queries)两个数组。 - 遍历
words数组,通过count_words[i] = self.f(words[i])计算每个words[i]的f(s)值并存入count_words数组。 - 遍历
queries数组,通过queries_words[i] = self.f(queries[i])计算每个queries[i]的f(s)值并存入queries_words数组。 - 对
count_words数组进行排序。 - 针对每个
queries_words[i],通过x = self.lowerbound(count_words, queries_words[i] + 1)进行二分查找,找到大于queries_words[i] + 1的最小索引x,再通过x = len(count_words) - x得到满足条件的个数并添加到结果数组res中。
- 初始化
- 在
3. 第三种方法
- 核心思路 :
- 利用了
sorted函数和bisect_right函数的特性来简洁地解决问题。首先通过生成器表达式sorted(s.count(min(s)) for s in words)快速计算出words中每个字符串的最小字母出现频次并排序得到数组a。 - 然后对于每个
queries中的字符串q,通过q.count(min(q))计算其最小字母出现频次,再利用bisect_right函数在排序后的数组a中找到大于该频次的最小索引,最后通过len(a) - bisect_right(a, q.count(min(q)))得到满足条件的words中字符串的个数并添加到结果数组中。
- 利用了
- 具体步骤 :
- 在
numSmallerByFrequency函数中:- 通过
a = sorted(s.count(min(s)) for s in words)计算并排序words中每个字符串的最小字母出现频次得到数组a。 - 遍历
queries数组,对于每个q:- 通过
q.count(min(q))计算q的最小字母出现频次。 - 通过
bisect_right(a, q.count(min(q)))在数组a中找到大于该频次的最小索引。 - 通过
len(a) - bisect_right(a, q.count(min(q)))得到满足条件的words中字符串的个数并添加到结果数组中。
- 通过
- 通过
- 在
三、三种方法的异同点
1. 相同点
- 都需要先实现一个计算字符串中字典序最小字母出现频次的函数
f(s),且实现逻辑基本相似,都是通过遍历字符串,不断更新最小字母及其出现频次。删除线格式 - 最终目的都是针对每个
queries中的字符串,统计words中满足f(queries[i]) < f(word)的词的数目,并返回一个结果数组。
2. 不同点
- 数据处理方式 :
- 第一种方法 :使用一个固定长度的数组
count来统计words中不同f(s)值的个数,并通过将其更新为后缀和数组来加速查询。这种方式利用了f(s)值范围有限的特点,通过巧妙的预处理和后缀和计算,能够快速得到每个queries[i]的结果。 - 第二种方法 :分别计算
words和queries的f(s)值并存到两个数组count_words和queries_words中,然后对count_words进行排序,再通过二分查找来确定满足条件的个数。这种方法相对更直观,但需要额外的数组存储和排序、二分查找等操作。 - 第三种方法 :通过生成器表达式和
sorted函数快速计算并排序words中字符串的最小字母出现频次得到数组a,然后利用bisect_right函数在a中查找满足条件的索引,进而得到满足条件的个数。这种方法代码最为简洁,但需要对bisect_right函数的使用比较熟悉。
- 第一种方法 :使用一个固定长度的数组
- 时间复杂度 :
- 第一种方法 :时间复杂度为 O ( ( n + m ) p ) O((n + m)p) O((n+m)p),其中 n n n 是
queries的长度, m m m 是words的长度, p p p 是queries和words中的最长字符串的长度。主要时间消耗在计算f(s)值以及对count数组的预处理和查询上。 - 第二种方法 :时间复杂度也为 O ( ( n + m ) p ) O((n + m)p) O((n+m)p),同样主要消耗在计算
f(s)值、数组排序(一般排序算法时间复杂度为 O ( m log m ) O(m\log m) O(mlogm),这里 m m m 是words的长度)以及二分查找(每次二分查找时间复杂度为 O ( log m ) O(\log m) O(logm),总共 n n n 次查询,所以是 O ( n log m ) O(n\log m) O(nlogm))上。总体来说,在时间复杂度上与第一种方法相近,但由于排序和二分查找的操作,可能在实际运行中效率稍低一些。 - 第三种方法 :时间复杂度同样为 O ( ( n + m ) p ) O((n + m)p) O((n+m)p),主要消耗在计算
words中字符串的最小字母出现频次(通过生成器表达式和sorted函数,时间复杂度与words的长度和字符串长度有关)以及对queries中的每个字符串进行查找(通过bisect_right函数,每次查找时间复杂度为 O ( log m ) O(\log m) O(logm),总共 n n n 次查询)上。虽然代码简洁,但在时间复杂度上与前两种方法基本处于同一量级。
- 第一种方法 :时间复杂度为 O ( ( n + m ) p ) O((n + m)p) O((n+m)p),其中 n n n 是
- 空间复杂度 :
- 第一种方法 :空间复杂度为 O ( 1 ) O(1) O(1),不统计返回值所占用的空间,只使用了常数个变量,通过巧妙地利用一个固定长度的数组
count来完成统计和查询,避免了大量额外的空间占用。 - 第二种方法 :空间复杂度为 O ( n + m ) O(n + m) O(n+m),因为分别创建了长度为
n的queries_words数组和长度为m的count_words数组来存储计算得到的f(s)值,相比第一种方法占用了更多的空间。 - 第三种方法 :空间复杂度为 O ( m ) O(m) O(m),主要是因为通过
sorted函数创建了一个长度为m的数组a来存储words中字符串的最小字母出现频次,虽然相比第二种方法占用空间有所减少,但还是比第一种方法占用了更多空间。
- 第一种方法 :空间复杂度为 O ( 1 ) O(1) O(1),不统计返回值所占用的空间,只使用了常数个变量,通过巧妙地利用一个固定长度的数组
综上所述,三种方法都能解决问题,但在数据处理方式、时间复杂度和空间复杂度等方面存在差异,你可以根据具体的需求和应用场景选择合适的方法。