LeetCode_位运算_中等_318.最大单词长度乘积

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] 仅包含小写字母

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/maximum-product-of-word-lengths

2.思路

(1)暴力穷举法
暴力穷举法比较容易想到,具体步骤如下:
① 先创建函数 hasCommonLetter(word1, word2),其用与判断单词 word1 和 word2 是否含有公共字母,如果有返回 true,否则返回 false;

public boolean hasCommonLetter(String word1, String word2);

② 使用 2 层 for 循环来枚举所有的 words[i] 和 words[j],并且使用函数 hasCommonLetter 来判断它们是否含有公共字母,如果没有,则用变量 res 来保存 length(words[i]) * length(words[j]) 的最大值;
③ 遍历结束后,直接返回 res 即可。

不过该方法的时间复杂度较高,在 LeetCode 上提交时会出现“超出时间限制”的提示!

(2)位运算 & 状态压缩
思路参考该 LeetCode 用户题解

① 定义长度为 words.length 的数组 masks,其中 masks[i] 的二进制表示的低 26 位(从右往左)分别代指单词 words[i] 中小写字母 a - z 是否出现过,这里为了方便理解,可看下面的例子:

例如,masks[1] = 131131 = 128 + 2 + 1 的二进制表示为 0000 0000 0000 0000 0000 0000 1000 0011
而低 26 位(从右往左)分别指代小写字母 a - z 是否出现过,为 1 说明出现过,为 0 则说明没有出现
那么 masks[1] = 131 说明单词 words[0] 中出现了小写字母 a、b 和 h

看到这里,可能有读者会问,数组 masks 的作用是什么呢?其主要作用在于判断单词 words[i] 和 words[j] 是否含有公共字母

例如,words[0] = "abc",words[1] = "bafd",那么对应的 
masks[0] = 0000 0000 0000 0000 0000 0000 0111 = 7,
masks[1] = 0000 0000 0000 0000 0000 0010 1011 = 43
此时进行如下与运算:masks[0] & masks[1] = 0000 0000 0000 0000 0000 0000 0011 = 3 != 0,
那么便可以说明单词 words[i] 和 words[j] 含有公共字母;反之,如果 masks[0] & masks[1] = 0,那么说明它们不含有公共字母。

其背后的原理在于,如果 words[i] 和 words[j] 含有公共字母(假设是字母 ‘a’),那么它们对应的 masks[i] 和 masks[j] 的二进制表示的最低位必然均为 1,此时 masks[i] 和 masks[j] 相与的结果自然不可能为 0(因为最低位的结果必为 1),所以根据这一点便可以进行判断。

② 有了数组 masks 之后,后面就只需要使用 2 层 for 循环来枚举不含有公共字母的 (words[i], words[j]) ,同时使用变量 res 来记录 words[i].length() * words[j].length() 的最大值;

③ 当遍历结束后,直接返回 res 即可。

3.代码实现(Java)

//思路1————暴力穷举法
class Solution {
    public int maxProduct(String[] words) {
        int length = words.length;
        int res = 0;
        for (int i = 0; i < length; i++) {
            for (int j = i + 1; j < length; j++) {
                if (!hasCommonLetter(words[i], words[j])) {
                    res = Math.max(res, words[i].length() * words[j].length());
                }
            }
        }
        return res;
    }
    
    //判断单词 word1 和 word2 是否含有公共字母
    public boolean hasCommonLetter(String word1, String word2) {
        int[] cnt1 = new int[26];
        int[] cnt2 = new int[26];
        for (int i = 0; i < word1.length(); i++) {
            cnt1[word1.charAt(i) - 'a']++;
        }
        for (int i = 0; i < word2.length(); i++) {
            cnt2[word2.charAt(i) - 'a']++;
        }
        for (int i = 0; i < 26; i++) {
            if (cnt1[i] != 0 && cnt2[i] != 0) {
                return true;
            }
        }
        return false;
    }
}
//思路2————位运算 & 状态压缩
class Solution {
    public static int maxProduct(String[] words) {
        int length = words.length;
        int index = 0;
        /*
            masks[i] 的二进制表示的低 26 位(从右往左)分别代指单词 words[i] 中小写字母 a - z 是否出现过
            例如,masks[0] = 129,129 的二进制表示为 0000 0000 0000 0000 0000 0000 1000 0001
            那么说明单词 words[0] 中出现了小写字母 a 和 h
        */
        int[] masks = new int[length];
        for (String word : words) {
            int t = 0;
            for (int i = 0; i < word.length(); i++) {
                int c = word.charAt(i) - 'a';
                t |= (1 << c);
            }
            masks[index++] = t;
        }
        int res = 0;
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < i; j++) {
                //(masks[i] & masks[j]) == 0 说明 words[i] 和 words[j] 不含有公共字母
                if ((masks[i] & masks[j]) == 0) {
                    //更新 res
                    res = Math.max(res, words[i].length() * words[j].length());
                }
            }
        }
        return res;
    }
}

版权声明:本文为weixin_43004044原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。