1、题干
给你一个字符串数组 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,判断两个word是否有相同字母需要再遍历word中的字符
- 按暴力解法估算时间复杂度大概是O(n^3),n取1000的话,超时基本没跑了
- 实际上2层嵌套循环基本无法避免,也就是说时间复杂度至少是O(n^2),那就想办法再减少一层遍历
第1层优化-字典:
- 题目中说到
每个单词只包含小写字母
,通常会想到把单词转换成以字母为key的哈希表或者数组字典来做优化 - 根据题意,使用数组表示单词时,只要将字母对应ASCII码顺序作为索引,值为bool类型即可
- 举个例子,单词
abc
可以表示为[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1]
,单词a
可以表示为[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]
- 判断两个单词是否包含相同字母需要做一次遍历,由于数组长度恒定为26,因此时间复杂度为常数
- 至此,时间复杂度已经可以达到O(n^2)
- 题目中说到
第2层优化-位运算:
- 实际上,如果用二进制整数来表示单词会更简洁
abc
可以表示为0b111
,a
可以表示为0b1
- 而且判断两个单词是否有相同字母只需要做一次&运算而不需要遍历数组,&运算结果为0则表示没有相同字母
- 这个优化不能减少时间复杂度,但是可以减少执行时间
- 实际上,如果用二进制整数来表示单词会更简洁
解题步骤:
- 1、遍历words,将words中的单词转换为长度为2的数组
[bit,word.length]
,数组两个元素分别表示单词的二进制和单词的长度 - 2、再次遍历words,把当前单词与剩余单词做匹配,如果不存在相同单词,则计算长度乘积
- 3、返回长度乘积的最大值,具体代码如下
- 1、遍历words,将words中的单词转换为长度为2的数组
进一步优化:
- 题解中说到单词转成二进制之后可能会相等,因此可以用哈希表做进一步优化(答题的时候没想到,另外考虑到单词组合问题,26个字母组成1000个单词,最坏情况下转成二进制之后可能没有相等的情况,因此就不写了)
- 答题的时候发现运算次数达到一定量级之后,位运算的速度比乘方运算更快些(属于自己挖坑了,这里耗时差距能达到10+倍,具体参考附录)
代码
var maxProduct = function (words) {
const CODEA = 'a'.charCodeAt(0);
for (let i = 0; i < words.length; i++) {
let bit = 0;
for (let j = 0; j < words[i].length; j++) {
const p = words[i].charCodeAt(j) - CODEA;
bit = bit | (1 << p);
}
words[i] = [bit, words[i].length];
}
let res = 0;
for (let i = 0; i < words.length; i++) {
for (let j = i + 1; j < words.length; j++) {
if ((words[i][0] & words[j][0]) === 0) {
res = Math.max(res, words[i][1] * words[j][1]);
}
}
}
return res;
};
附录
const K = 1e6;
const MOD = 10;
console.time('乘方');
let n = 0;
for (let i = 0; i < K; i++) {
n = n | (2 ** (i % MOD));
}
console.timeEnd('乘方');
console.time('位运算');
n = 0;
for (let i = 0; i < K; i++) {
n = n | (1 << (i % MOD));
}
console.timeEnd('位运算');
// 浏览器下执行结果
// 乘方: 78.853759765625 ms
// 位运算: 6.093994140625 ms