318. 给你一个字符串数组 words
,找出并返回 length(words[i]) * length(words[j])
的最大值,并且这两个单词不含有公共字母。如果不存在这样的两个单词,返回 0
。
题目网址:318. 最大单词长度乘积 - 力扣(LeetCode)
我的解法:
class Solution:
def maxProduct(self, words: List[str]) -> int:
s = 0
x = 1
words = list(OrderedDict.fromkeys(words))
#words = list(set(words))
for q in words:
setq = set(','.join(q).split(','))
for k in words[x::]:
setk = set(','.join(k).split(','))
if setq.isdisjoint(setk):
s = len(q)*len(k) if len(q)*len(k)>s else s
x += 1
return s
代码分析:
本代码思想为将字符串分割成字母,并运用使用 collections.OrderedDict.fromkeys(),进行输入列表的去重,这是完成特殊任务的最快方式。它首先删除列表中的重复项并返回一个字典,最后将其转换为列表。此方法也可用于字符串,之后列表中元素的顺序发生了变化。这里查重的原因是防止很多重复元素影响运行速度(倒数第二个数据有所体现)。
其实这里也可以用数组性质进行字符串内部的去重(即上代码注释部分),实际上这两种方法在本题种速度相差不多,set()方法速度为3384ms,collections.OrderedDict.fromkeys()方法速度为3240ms,在内存占用上后者略优于前者(set()方法占用内存18.43MB,另一种占用内存为18.38MB,均优于官方解法)。
后续用双重循环,在循环中,我们将每一个字符串拆分成一个个字母,并运用set()方法进行每个字符串(此时应该是拆分成的字母所组成的列表)中的查重,另一个字符串同理,最后用s1.isdiajoint(s2)方法判断两个数组是否存在交集,判断对应长度乘积是否为最大值,这样我们就完成了代码。成绩如下:
复杂度
时间复杂度:
添加时间复杂度: O(n^2+n)
空间复杂度:
添加空间复杂度: O(n)
以下为力扣官方题解318. 最大单词长度乘积 - 力扣(LeetCode)
方法一:位运算
为了得到最大单词长度乘积,朴素的做法是,遍历字符串数组 words 中的每一对单词,判断这一对单词是否有公共字母,如果没有公共字母,则用这一对单词的长度乘积更新最大单词长度乘积。
用 n 表示数组 words的长度,用 li表示单词 words[i]的长度,其中 0≤i<n,则上述做法需要遍历字符串数组words 中的每一对单词,对于下标为 i 和 j 的单词,其中 i<j,需要 O(li×lj)的时间判断是否有公共字母和计算长度乘积。因此上述做法的时间复杂度是 O(∑0≤i<j<n li×lj),该时间复杂度高于O(n^2)。
如果可以将判断两个单词是否有公共字母的时间复杂度降低到 O(1),则可以将总时间复杂度降低到 O(n^2)。可以使用位运算预处理每个单词,通过位运算操作判断两个单词是否有公共字母。由于单词只包含小写字母,共有 26 个小写字母,因此可以使用位掩码的最低 26 位分别表示每个字母是否在这个单词中出现。将 a 到 z 分别记为第 0 个字母到第 25 个字母,则位掩码的从低到高的第 i 位是 1 当且仅当第 i 个字母在这个单词中,其中 0≤i≤25。
用数组 masks 记录每个单词的位掩码表示。计算数组 masks 之后,判断第 i 个单词和第 j 个单词是否有公共字母可以通过判断 masks[i] & masks[j] 是否等于 0 实现,当且仅当 masks[i] & masks[j]=0 时第 i 个单词和第 j 个单词没有公共字母,此时使用这两个单词的长度乘积更新最大单词长度乘积。
class Solution:
def maxProduct(self, words: List[str]) -> int:
masks = [reduce(lambda a, b: a | (1 << (ord(b) - ord('a'))), word, 0) for word in words]
return max((len(x[1]) * len(y[1]) for x, y in product(zip(masks, words), repeat=2) if x[0] & y[0] == 0), default=0)
方法二:位运算优化
方法一需要对数组 words 中的每个单词计算位掩码,如果数组 words 中存在由相同的字母组成的不同单词,则会造成不必要的重复计算。例如单词 meet 和 met 包含的字母相同,只是字母的出现次数和单词长度不同,因此这两个单词的位掩码表示也相同。由于判断两个单词是否有公共字母是通过判断两个单词的位掩码的按位与运算实现,因此在位掩码相同的情况下,单词的长度不会影响是否有公共字母,当两个位掩码的按位与运算等于 0 时,为了得到最大单词长度乘积,这两个位掩码对应的单词长度应该尽可能大。根据上述分析可知,如果有多个单词的位掩码相同,则只需要记录该位掩码对应的最大单词长度即可。
可以使用哈希表记录每个位掩码对应的最大单词长度,然后遍历哈希表中的每一对位掩码,如果这一对位掩码的按位与运算等于 0,则用这一对位掩码对应的长度乘积更新最大单词长度乘积。
由于每个单词的位掩码都不等于 0,任何一个不等于 0 的数和自身做按位与运算的结果一定不等于 0,因此当一对位掩码的按位与运算等于 0 时,这两个位掩码一定是不同的,对应的单词也一定是不同的。
class Solution:
def maxProduct(self, words: List[str]) -> int:
masks = defaultdict(int)
for word in words:
mask = reduce(lambda a, b: a | (1 << (ord(b) - ord('a'))), word, 0)
masks[mask] = max(masks[mask], len(word))
return max((masks[x] * masks[y] for x, y in product(masks, repeat=2) if x & y == 0), default=0)