题目背景
给定一个字符串数组 words
,要找到两个不含相同字母的单词,使它们长度乘积最大,返回这个乘积。
方法一:用哈希表记录字符串中出现的字符
这个程序做的事:
- 先用
flags
表记每个单词包含哪些字母 - 两两比较单词,看是否有公共字母
- 如果没有,计算长度乘积
- 返回最大值
public class A_MaxProduct {
public int maxProduct(String[] words) {//输入是一个字符串数组 `words`
boolean[][] flags = new boolean[words.length][26];//创建二维数组 `flags`
for (int i = 0; i < words.length; i++) {//遍历每个单词的每个字母
for (char c: words[i].toCharArray()) {//循环‘字符串转化的字符数组’
flags[i][c - 'a'] = true;//如果有,就把对应位置标记为 `true`
}
}
int result = 0;//初始化最大乘积
for (int i = 0; i < words.length; i++) {
for (int j = i + 1; j < words.length; j++) {//双重循环,枚举所有不重复的单词对(i<j)
int k = 0;
for (; k < 26; k++) {
if (flags[i][k] && flags[j][k]) {//判断这两个单词是否有相同字母
break;//如果发现两个单词都有同一个字母,就`break`
}
}
//如果没有公共字母,就计算长度乘积
if (k == 26) {
int prod = words[i].length() * words[j].length();//如果 k==26,说明没提前 break → 两个单词没有公共字母
result = Math.max(result, prod);//就计算长度乘积,更新最大值
}
}
}
return result;
}
public static void main(String[] args) {
A_MaxProduct obj = new A_MaxProduct();
String[] words = {"abcw", "baz", "foo", "bar", "xtfn", "abcdef"};
int ans = obj.maxProduct(words);
System.out.println("Max Product: " + ans);
}
}
for (int i = 0; i < words.length; i++) {
for (char c: words[i].toCharArray()) {
flags[i][c - 'a'] = true;
}
words[i]
表示当前单词,比如第 i 个单词 "abcw"
words[i].toCharArray()
把字符串转成一个 char[]
(字符数组)
"abcw".toCharArray() → ['a','b','c','w']
for (char c: words[i].toCharArray())
循环这个字符数组:
- 第一次 c='a'
- 第二次 c='b'
- 第三次 c='c'
- 第四次 c='w'
flags[i][c - 'a'] = true;
c - 'a'
是为了把字符变成数字索引:
- 'a' - 'a' = 0
- 'b' - 'a' = 1
- 'c' - 'a' = 2
- 'w' - 'a' = 22
所以:
flags[i][0]=true
表示单词 words[i] 有字母 'a'flags[i][1]=true
表示单词 words[i] 有字母 'b'flags[i][2]=true
表示单词 words[i] 有字母 'c'flags[i][22]=true
表示单词 words[i] 有字母 'w'
结果:
flags[i]
是长度26的布尔数组- 每个位置代表一个字母 a~z
- 如果单词里有这个字母 → 就是 true,没有就是 false
int k = 0;
for (; k < 26; k++) {
if (flags[i][k] && flags[j][k]) {
break;
}
比如:
- words[i] 是 "abc" → flags[i][0]=true, flags[i][1]=true, flags[i][2]=true
- words[j] 是 "def" → flags[j][3]=true, flags[j][4]=true, flags[j][5]=true
遍历 k:
- k=0:flags[i][0]=true,但 flags[j][0]=false → 不满足
- k=1:flags[i][1]=true,但 flags[j][1]=false → 不满足
- k=2:flags[i][2]=true,但 flags[j][2]=false → 不满足
- k=3:flags[i][3]=false → 不满足
...
一直到 k=25 都不满足
循环结束后:k=26
方法二:用整数的二进制数位记录字符串中出现的字符
位运算优化版本
位运算版本相比 boolean[][]:
- 空间只用 int[],更小
- 比较两个单词是否有公共字母,只需一次
flags[i]&flags[j]
,更快
flags[i] |= 1 << (c - 'a');
就是用一个 int 的 26 位来记录每个单词有哪些字母,用位运算让比较更快
第一次遇到 'a':
'a' - 'a' = 0
1 << 0
就是把第 0 个位置放一个 1:
000...0001
也就是只最右边一个 1。
用 flags[i] |=
(或者叫“打勾”):
- 如果原本是
000...0000
- 和
000...0001
做“或运算”,结果就是000...0001
表示“我有 a” ✅
第二次遇到 'b':
'b' - 'a' = 1
1 << 1 = 000...0010
和刚才的结果 000...0001
做“或运算”:
000...0001
|
000...0010
=
000...0011
表示我有 a 和 b。
第三次遇到 'c':
'c' - 'a' = 2
1 << 2 = 000...0100
再做“或运算”:
000...0011
|
000...0100
=
000...0111
表示我有 a,b,c
使用位运算将每个单词的字符集合编码为一个整型 bitmask,
然后用按位与(&)快速判断两个单词是否有公共字符
public class A_MaxProduct_1 {
public int maxProduct(String[] words) {
int[] flags = new int[words.length];
for (int i = 0; i < words.length; i++) {
for (char c : words[i].toCharArray()) {
flags[i] |= 1 << (c - 'a');
}
}
int result = 0;
for (int i =0; i < words.length; i++) {
for (int j = i + 1; j < words.length; j++) {
if ((flags[i] & flags[j]) == 0) {
int prod = words[i].length() * words[j].length();
result = Math.max(result, prod);
}
}
}
return result;
}
public static void main(String[] args) {
A_MaxProduct_1 obj = new A_MaxProduct_1();
String[] words = {"abcw", "baz", "foo", "bar", "xtfn", "abcdef"};
int ans = obj.maxProduct(words);
System.out.println("Max product: " + ans);
}
}