318. 最大单词长度乘积(中等)
方法一
思路
-
这道题有两个要求:
length(words[i]) * length(words[j])
的最大值;- 这两个单词不含有公共字母。
-
首先我们考虑第一个条件:长度乘积的最大值。
是否需要将 words 中的单词按照长度从大到小进行排序,然后逐一遍历?并不需要。比如,长度最长的单词为5,剩余的单词中不和它有公共字母的最长单词长度为2,它们的长度乘积为 10;然而长度第二的单词为 4 ,剩余的单词中不和它有公共字母的最长单词长度为3,它们的长度乘积为 12 。显然,需要遍历所有单词的可能结果才能得到最终答案,因此对长度排序是没有意义的,所以我们一开始不应该关注这个条件。
-
那么我们考虑第二个条件:快速判断两个单词不包含相同字母。
由于这一章是围绕二进制展开的,因此我想到了 长度为 26 的哈希表,如果该字母出现过,就在其位置上标记为 1 。
在判断两个单词是否包含相同字母的时候,我们可以对两个单词的哈希表逐一遍历,如果它们的哈希表同时出现 1 ,说明包含相同字母,此时将标志 flag 记为 false,并且退出这两个单词的判断。
-
因此,总的算法思想就是:将所有单词两两遍历,判断它们是否包含公共字母,如果不包含的话,就计算它们的长度乘积,保留最大的长度乘积作为最终结果。
代码
class Solution {
public:
int maxProduct(vector<string>& words) {
int ans = 0;
int n = words.size();
vector<vector<int>> letters(n, vector<int>(26, 0));
// words预处理
for(int i=0; i<n; ++i){
for(char ch : words[i]){
letters[i][ch - 'a'] = 1;
}
}
// cout<<letters[0][0];
for(int i=0; i<n; ++i){
for(int j=i+1; j<n; ++j){
// flag = true:没有出现公共字母
bool flag = true;
for(int k=0; k<26; ++k){
// 如果两个单词包含公共字母,那么标记并遍历下一个单词
if(letters[i][k] == 1 && letters[j][k] == 1){
flag = false;
break;
}
}
// 如果不包含公共字母,那么计算它们的长度乘积
if(flag){
int length = words[i].size() * words[j].size();
ans = max(ans, length);
}
}
}
return ans;
}
};
方法二:优化
思路
-
虽然我在方法一就用到了二进制的思想,但是它其实更符合哈希表,因为我并没有用到二进制的性质。
-
那么如何巧妙利用二进制的性质呢?
我们可以将两个单词的二进制表示按位与,如果两个单词存在重复数字,那么它们按位与的结果不为 0。
-
因此,总的算法思想:为每个字母串建立长度为 26 的二进制数字,每个位置表示是否存在该字母。如果两个字母串不含有重复数字,那么它们的二进制表示的按位与为 0 ,此时计算它们的长度乘积并与最大值相比较,保留更大的那个值。
代码
class Solution {
public:
int maxProduct(vector<string>& words) {
int ans = 0;
int n = words.size();
vector<int> mask(n, 0);
// words预处理
for(int i=0; i<n; ++i){
int temp = 0;
for(char ch : words[i]){
// 单词words[i]的二进制表示
temp |= 1 << (ch -'a');
}
mask[i] = temp;
}
for(int i=0; i<n; ++i){
for(int j=i+1; j<n; ++j){
// 没有出现公共字母
if((mask[i] & mask[j]) == 0){
int length = words[i].size() * words[j].size();
ans = max(ans, length);
}
}
}
return ans;
}
};