318. 最大单词长度乘积
难度:中等
题目
给你一个字符串数组 words
,找出并返回 length(words[i]) * length(words[j])
的最大值,并且这两个单词不含有公共字母。如果不存在这样的两个单词,返回 0
。
示例 1:
输入:words = ["abcw","baz","foo","bar","xtfn","abcdef"]
输出:16
解释:这两个单词为 "abcw", "xtfn"。
示例 2:
输入:words = ["a","ab","abc","d","cd","bcd","abcd"]
输出:4
解释:这两个单词为 "ab", "cd"。
示例 3:
输入:words = ["a","aa","aaa","aaaa"]
输出:0
解释:不存在这样的两个单词。
提示:
2 <= words.length <= 1000
1 <= words[i].length <= 1000
words[i]
仅包含小写字母
个人题解
思路:
- 为了得到最大单词长度乘积,遍历字符串数组 words 中的每一对单词,判断这一对单词是否有公共字母,如果没有公共字母,则用这一对单词的长度乘积更新最大单词长度乘积。
- 对上述情况剪枝,只有长度乘积更大的才需要判断是否有公共字母
class Solution {// 入口public int maxProduct(String[] words) {int max = 0;int len = words.length;for (int i = 0; i < len; i++) {for (int j = i + 1; j < len; j++) {if (words[i].length() * words[j].length() > max && !hasRepeatChar(words[i], words[j])) {max = words[i].length() * words[j].length();}}}return max;}public boolean hasRepeatChar(String s1, String s2) {int s1Len = s1.length();int s2Len = s2.length();HashSet<Character> hashSet = new HashSet<>(Math.min(s1Len, 26));for (int i = 0; i < s1Len; i++) {hashSet.add(s1.charAt(i));}for (int i = 0; i < s2Len; i++) {if (hashSet.contains(s2.charAt(i))) {return true;}}return false;}
}
官方题解
方法一:位运算
如果可以将判断两个单词是否有公共字母的时间复杂度降低到 O(1),则可以将总时间复杂度降低到 O(n^2)。可以使用位运算预处理每个单词,通过位运算操作判断两个单词是否有公共字母。由于单词只包含小写字母,共有 26 个小写字母,因此可以使用位掩码的最低 26 位分别表示每个字母是否在这个单词中出现。将 a 到 z 分别记为第 0 个字母到第 25 个字母,则位掩码的从低到高的第 i 位是 1 当且仅当第 i 个字母在这个单词中,其中 0 <= i <= 25。
用数组 masks 记录每个单词的位掩码表示。计算数组 masks 之后,判断第 i 个单词和第 j 个单词是否有公共字母可以通过判断 mask[i] & mask[j] 是否等于 0 实现,当且仅当 mask[i] & mask[j] = 0 时,第 i 个单词和第 j 个单词没有公共字母,此时使用这两个单词的长度乘积更新最大单词长度乘积。
class Solution {public int maxProduct(String[] words) {int length = words.length;int[] masks = new int[length];for (int i = 0; i < length; i++) {String word = words[i];int wordLength = word.length();for (int j = 0; j < wordLength; j++) {masks[i] |= 1 << (word.charAt(j) - 'a');}}int maxProd = 0;for (int i = 0; i < length; i++) {for (int j = i + 1; j < length; j++) {if ((masks[i] & masks[j]) == 0) {maxProd = Math.max(maxProd, words[i].length() * words[j].length());}}}return maxProd;}
}
复杂度分析
- 时间复杂度:O(L + n^2),其中L是数组 words 中的全部单词长度之和,n是数组 words 的长度
- 空间复杂度:O(n),n是数组 words 的长度。需要常见长度为 n 的位掩码数组 masks
方法二:位运算优化
方法一需要对数组 words 中的每个单词计算位掩码,如果数组 words 中存在由相同的字母组成的不同单词,则会造成不必要的重复计算。例如单词 mcct 和 mct 包含的字母相同,只是字母的出现次数和单词长度不同,因此这两个单词的位掩码表示也相同。由于判断两个单词是否有公共字母是通过判断两个单词的位掩码的按位与运算实现,因此在位掩码相同的情况下,单词的长度不会影响是否有公共字母,当两个位掩码的按位与运算等于 0 时,为了得到最大单词长度乘积,这两个位掩码对应的单词长度应该尽可能大。根据上述分析可知,如果有多个单词的位掩码相同,则只需要记录该位掩码对应的最大单词长度即可。
可以使用哈希表记录每个位掩码对应的最大单词长度,然后遍历哈希表中的每一对位掩码,如果这一对位掩码的按位与运算等于 0 ,则用这一对位掩码对应的长度乘积更新最大单词长度乘积。
由于每个单词的位掩码都不等于 0 ,任何一个不等于 0 的数和自身做按位与运算的结果一定不等于 0 ,因此当一对位掩码的按位与运算等于 0 时,这两个位掩码一定是不同的,对应的单词也一定是不同的。
class Solution {public int maxProduct(String[] words) {Map<Integer, Integer> map = new HashMap<Integer, Integer>();int length = words.length;for (int i = 0; i < length; i++) {int mask = 0;String word = words[i];int wordLength = word.length();for (int j = 0; j < wordLength; j++) {mask |= 1 << (word.charAt(j) - 'a');}if (wordLength > map.getOrDefault(mask, 0)) {map.put(mask, wordLength);}}int maxProd = 0;Set<Integer> maskSet = map.keySet();for (int mask1 : maskSet) {int wordLength1 = map.get(mask1);for (int mask2 : maskSet) {if ((mask1 & mask2) == 0) {int wordLength2 = map.get(mask2);maxProd = Math.max(maxProd, wordLength1 * wordLength2);}}}return maxProd;}
}
复杂度分析
- 时间复杂度:O(L + n ^ 2)
- 空间复杂度:O(n)
作者:力扣官方题解
链接:https://leetcode.cn/problems/maximum-product-of-word-lengths/solutions/1104441/zui-da-dan-ci-chang-du-cheng-ji-by-leetc-lym9/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。