-
题目
给定一个字符串数组 words,请计算当两个字符串 words[i] 和 words[j] 不包含相同字符时,它们长度的乘积的最大值。假设字符串中只包含英语的小写字母。如果没有不包含相同字符的一对字符串,返回 0。
示例
输入: words = [“abcw”,“baz”,“foo”,“bar”,“fxyz”,“abcdef”]
输出: 16
解释: 这两个单词为 “abcw”, “fxyz”。它们不包含相同字符,且长度的乘积最大。
-
思路
- 第一种,暴力循环破解法。使用map来记录字母的出现,对数组进行循环遍历一一对比,如果出现重复字母,则跳过该轮两个单词的对比;否则计算单词长度乘积,并与ans对比,选出最大者。该方法的时间和内存消耗都很惊人。
- 第二种,位运算法。相较于第一种方法,本方法是对判断字符是否有公共字母的方法进行了优化。第一种是使用map来一一比,看是否有重复值。本方法是把单词转变为一个掩码,掩码的每一位代表一个字母,如果该字母存在则是1,不存在则是0。
根据题意,单词只包含小写字母,共有26个字母,所以26位掩码来分别表示每个字母是否在单词中出现。当每个单词都有自己的掩码之后,再进行循环进行一一判断,当两个单词的掩码进行&运算后,如果为0则表示没有公共字母,则可以计算这两个单词的长度乘积。 - 第三种,位运算优化法。本方法的核心思路与第二种方法一致,但是第二种方法是每一个单词记录一个掩码,再进行循环对比。而本方法是使用map存下掩码对应的最长长度值,再进行一一对比。比如,
a
和aaaaa
,这两个的单词掩码是相等的,如果按照第二种方法,则是这两个单词对应两个掩码,且这两个掩码相等,循环时会做重复比较。而第二种方法,则是只存一个掩码,并把长度记录为aaaaa
的长度,这样循环时会减去一些对比。
-
代码
class Solution {
public:
int maxProduct(vector<string>& words) {
//第一种,暴力循环破解,时间消耗和内存消耗惊人
int ans=0,tmp,k=0,l;
//ans记录最大值,tmp用在循环里,表示两个单词是否有公共字母,k用于索引,l记录长度
map<char,int> re;
//map记录字母是否在单词中是否出现
for(int i=0;i<words.size();i++){
while(words[i][k]!='\0')
re[words[i][k++]]++;
l=k;
//记录固定单词的字母出现已经长度
k=0;
for(int j=i+1;j<words.size();j++){
//把固定单词与余下的每个单词进行比较
while(words[j][k]!='\0')
if(re[words[j][k++]]!=0){
tmp=-1;
break;
//如果该字母与固定单词有公共字母,则tmp记为-1,且跳过与该单词的后续比较
}
if(tmp==-1)
tmp=0;
else
tmp=l*k;
ans=ans>=tmp?ans:tmp;
tmp=0;
k=0;
//tmp和k都要记得还原
}
re.clear();
}
return ans;
//第二种,位运算法,对于判断字符是否有公共字母的方法进行优化
int ans=0,len=words.size();
vector<int> mask(len);
//vector来记录每个单词的掩码
for(int i=0;i<len;i++)
for(int j=0;j<words[i].size();j++)
mask[i]|=1<<(words[i][j]-'a');
//x=words[i][j]-'a'得到0-25的数值,比如'a'-'a'为0,'c'-'a'=2,下面以words[i][j]='c'为例
//1<<x,即1左移x位,左移填0,得到100
//再与mask进行位或运算
//该单词所有字母循环结束后,就会得到一个对应的掩码。比如”add“为1001,即低位1代表a,高位1代表d,表面该单词存在ad两个字母
for(int i=0;i<len;i++)
for(int j=i+1;j<len;j++)
//(mask1&mask2)一定要加小括号,不加,return只为0
//==的优先级高于&,不叫括号,会先进行mask2==0的运算,由于mask2不会为0,所以此步运算得出0;再进行mask1&0的运算,得0
if((mask[i]&mask[j])==0)
ans=max(ans,int(words[i].size()*words[j].size()));
return ans;
//第三种,针对位运算法的优化,让mask唯一化。比如met和meet,按照第二种方法,二者的mask一样且都存入数组中,但按照下面这种方法,mask只存一次,记录最长的单词长度,即meet的长度
int ans=0,len=words.size();
unordered_map<int,int> map;
for(int i=0;i<len;i++){
int l2=words[i].size();
int mask=0;
for(int j=0;j<l2;j++){
mask|=1<<(words[i][j]-'a');
}
if(map.count(mask)){
if(l2>map[mask])
map[mask]=l2;
}else{
map[mask]=l2;
}
}
for(auto[mask1, _]:map){
int l1=map[mask1];
for(auto[mask2, _]:map){
int l3=map[mask2];
if((mask1&mask2)==0){
ans=max(ans,l1*l3);
}
}
}
return ans;
}
};