串联字符串的最大长度 图解题解
这道题到底在问什么
- 输入
- arr=["un","iq","ue"]
- 输出
- 4
- 输入
- arr=["cha","r","act","ers"]
- 输出
- 6
- 输入
- arr=["aa","bb"]
- 输出
- 0
最优解:一步一步想明白
- 3核心三句话:串变指纹、按位与为 0 才能拼、DFS 选与不选并随时刷新最大长度。下面每帧都在套它。
- 4把每个串压成字母集先扫一遍数组,把每个串变成它用到的字母集合(指纹)。指纹相当于这个串占用了哪些字母。
- 5"un" → {un}"un" 内部没有重复字母,合法。它占用的字母集是 {u、n},记下这枚指纹。
- 6"iq" → {iq}"iq" 内部没有重复字母,合法。它占用的字母集是 {i、q},记下这枚指纹。
- 7"ue" → {ue}"ue" 内部没有重复字母,合法。它占用的字母集是 {u、e},记下这枚指纹。
- 8长度 0从空串出发,还没选任何串,长度 0。接下来逐个候选串试着往里选。
- 9"un" 指纹 & 当前 = 0试候选 "un"(占用 {u、n})。它和当前已选 空串 没有公共字母,按位与为 0,可以选入。
- 10长度 2当前已串联 "un",长度 2,字母互不相同,是一个合法组合。用它刷新最大长度,目前最大长度 = 2。
- 11"iq" 指纹 & 当前 = 0试候选 "iq"(占用 {i、q})。它和当前已选 "un" 没有公共字母,按位与为 0,可以选入。
- 12长度 4当前已串联 "uniq",长度 4,字母互不相同,是一个合法组合。用它刷新最大长度,目前最大长度 = 4。
- 13公共字母 {u}试候选 "ue",但它和当前已选 "uniq" 共用字母 {u},按位与不为 0,会重复,排除掉、不选它。
- 14退回 "un"选入 "iq" 的所有后续分支都试完了,回溯:把它从组合里撤掉,当前累计指纹和长度恢复成选它之前的 "un",再去试下一个候选。
- 15公共字母 {u}试候选 "ue",但它和当前已选 "un" 共用字母 {u},按位与不为 0,会重复,排除掉、不选它。
- 16退回 "空串"选入 "un" 的所有后续分支都试完了,回溯:把它从组合里撤掉,当前累计指纹和长度恢复成选它之前的 空串,再去试下一个候选。
- 17"iq" 指纹 & 当前 = 0试候选 "iq"(占用 {i、q})。它和当前已选 空串 没有公共字母,按位与为 0,可以选入。
- 18长度 2当前已串联 "iq",长度 2,字母互不相同,是一个合法组合。用它刷新最大长度,目前最大长度 = 4。
- 19"ue" 指纹 & 当前 = 0试候选 "ue"(占用 {u、e})。它和当前已选 "iq" 没有公共字母,按位与为 0,可以选入。
- 20长度 4当前已串联 "ique",长度 4,字母互不相同,是一个合法组合。用它刷新最大长度,目前最大长度 = 4。
- 21退回 "iq"选入 "ue" 的所有后续分支都试完了,回溯:把它从组合里撤掉,当前累计指纹和长度恢复成选它之前的 "iq",再去试下一个候选。
- 22退回 "空串"选入 "iq" 的所有后续分支都试完了,回溯:把它从组合里撤掉,当前累计指纹和长度恢复成选它之前的 空串,再去试下一个候选。
- 23"ue" 指纹 & 当前 = 0试候选 "ue"(占用 {u、e})。它和当前已选 空串 没有公共字母,按位与为 0,可以选入。
- 24长度 2当前已串联 "ue",长度 2,字母互不相同,是一个合法组合。用它刷新最大长度,目前最大长度 = 4。
- 25退回 "空串"选入 "ue" 的所有后续分支都试完了,回溯:把它从组合里撤掉,当前累计指纹和长度恢复成选它之前的 空串,再去试下一个候选。
- 26答案 = 4决策树全部走完。所有合法组合里最长的是 "uniq"("un"+"iq")或 "ique"("iq"+"ue"),长度都是 4,这就是最终答案。
⚠️ 容易写错的地方
✗ 错:忘了过滤串内部自重复的串
✓ 对:建指纹时若某位已置 1 就把整串作废
"aa" 这种串自己就有重复字母,永远不可能合法,留着只会干扰
✗ 错:用集合相交判断冲突,每次重建集合
✓ 对:用整数指纹按位与,一次运算 O(1)
位运算判冲突远快于反复建集合求交,是本题的关键优化
✗ 错:只在到达末尾时才更新答案
✓ 对:每进入一个状态就用当前长度刷新答案
空选或选一部分也是合法组合,答案可能就在中间状态产生
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def maxLength(self, arr: List[str]) -> int:
masks = []
for s in arr:
mask = 0
ok = True
for ch in s:
bit = 1 << (ord(ch) - 97)
if mask & bit:
ok = False; break
mask |= bit
if ok:
masks.append((mask, len(s)))
ans = 0
def dfs(i, mask, length):
nonlocal ans
ans = max(ans, length)
for j in range(i, len(masks)):
m, l = masks[j]
if mask & m == 0:
dfs(j + 1, mask | m, length + l)
dfs(0, 0, 0)
return ansC++
#include <string>
#include <vector>
#include <functional>
#include <algorithm>
using namespace std;
class Solution {
public:
int maxLength(vector<string>& arr) {
vector<pair<int,int>> masks;
for (auto &s : arr) {
int mask = 0; bool ok = true;
for (char c : s) { int bit = 1 << (c - 'a'); if (mask & bit) { ok = false; break; } mask |= bit; }
if (ok) masks.push_back({mask, (int)s.size()});
}
int ans = 0;
function<void(int,int,int)> dfs = [&](int i, int mask, int len) {
ans = max(ans, len);
for (int j = i; j < (int)masks.size(); ++j) if ((mask & masks[j].first) == 0) dfs(j + 1, mask | masks[j].first, len + masks[j].second);
};
dfs(0, 0, 0);
return ans;
}
};Java
import java.util.*;
class Solution {
List<int[]> masks;
int ans;
public int maxLength(List<String> arr) {
masks = new ArrayList<>();
for (String s : arr) {
int mask = 0; boolean ok = true;
for (char c : s.toCharArray()) {
int bit = 1 << (c - 'a');
if ((mask & bit) != 0) { ok = false; break; }
mask |= bit;
}
if (ok) masks.add(new int[]{mask, s.length()});
}
ans = 0; dfs(0, 0, 0);
return ans;
}
private void dfs(int i, int mask, int len) {
ans = Math.max(ans, len);
for (int j = i; j < masks.size(); j++) if ((mask & masks.get(j)[0]) == 0) dfs(j + 1, mask | masks.get(j)[0], len + masks.get(j)[1]);
}
}复杂度
时间
O(2^n)
n 为合法候选串个数;每个串选或不选,决策树最多 2^n 个状态,剪枝后通常远小于此
空间
O(n)
递归栈深最多 n 层;mask 是单个整数 O(1),存指纹列表 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 串联字符串的最大长度 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么用整数 bitmask 而不是用哈希集合表示字母占用?+
字母只有 26 个,正好塞进一个 int 的低 26 位。指纹用整数表示后,「合并两个串的字母」就是按位或,「判断有无公共字母」就是按位与,都是单条 CPU 指令,比哈希集合的插入和求交快一两个数量级,空间也只占一个整数。
如果改成可以重复选同一个串呢?+
本题选了一个串就用合并后的指纹往后走,同一个串再选会和自己冲突(按位与不为 0),所以天然不会重复选。若题目允许某串出现多次,那拼出来必然有重复字母、永远不合法,所以「无重复字母」这个约束本身就排除了重复选的可能。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 串联字符串的最大长度 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。