字符串的编码与解码 图解题解
把一堆字符串拼成一条再拆回来,难点在于分隔符可能出现在内容里——长度前缀是最干净的解法。
物流公司打包多件货物时,不能直接用逗号隔开货物名——万一货物名里本身就有逗号就乱套了。正确做法是在每件货物外面套一个「长度标签」:先写这件货物有多少字,再写货物内容。收货方先看标签知道取几个字,精确拆包,完全不怕内容里有任何特殊字符。这题的 len# 前缀,就是那个长度标签。
这道题到底在问什么
- 输入
- strs = ["abc","de"]
- 输出
- 编码 → "3#abc2#de",解码回 ["abc","de"]
最优解:一步一步想明白
- 3记住这条链:长度 → # → 内容。读的时候先拿长度,再照长度切,永远不会切错。下面把 3#abc2#de1#f 一格一格演给你看。
- 4编码第 1 个词 "abc":它有 3 个字符,先把长度 3 写到结果里(绿色这格)。
- 5紧跟着写一个 #,把「长度」和「内容」隔开(界碑作用)。现在结果里有了 3# 这个开头。
- 6最后把词 "abc" 原样接在 # 后面。这个词编码成 3#abc,整段已写进结果(绿色这片)。
- 7编码第 2 个词 "de":它有 2 个字符,先把长度 2 写到结果里(绿色这格)。
- 8紧跟着写一个 #,把「长度」和「内容」隔开(界碑作用)。现在结果里有了 2# 这个开头。
- 9最后把词 "de" 原样接在 # 后面。这个词编码成 2#de,整段已写进结果(绿色这片)。
- 10编码第 3 个词 "f":它有 1 个字符,先把长度 1 写到结果里(绿色这格)。
- 11紧跟着写一个 #,把「长度」和「内容」隔开(界碑作用)。现在结果里有了 1# 这个开头。
- 12最后把词 "f" 原样接在 # 后面。这个词编码成 1#f,整段已写进结果(绿色这片)。
- 13编码完成:["abc","de","f"] 变成一整条字符串 "3#abc2#de1#f"。下面演相反的过程——把它拆回来。
- 14指针 i 走到下标 0。先读 # 之前的数字,那就是下一个词有多长。
- 15# 之前的数字是 3,所以下个词长 3 个字符。下标 1 的 # 是界碑,跳过它,从下标 2 开始截内容。
- 16从 # 后面照长度 3 截取,截出的 "abc" 就是一个完整原词(绿色这片)。把它加进结果列表。
- 17这个词读完了,指针 i 直接跳到下标 5,重复同样的「读长度→跳#→按长度截」。
- 18指针 i 走到下标 5。先读 # 之前的数字,那就是下一个词有多长。
- 19# 之前的数字是 2,所以下个词长 2 个字符。下标 6 的 # 是界碑,跳过它,从下标 7 开始截内容。
- 20从 # 后面照长度 2 截取,截出的 "de" 就是一个完整原词(绿色这片)。把它加进结果列表。
- 21这个词读完了,指针 i 直接跳到下标 9,重复同样的「读长度→跳#→按长度截」。
- 22指针 i 走到下标 9。先读 # 之前的数字,那就是下一个词有多长。
- 23# 之前的数字是 1,所以下个词长 1 个字符。下标 10 的 # 是界碑,跳过它,从下标 11 开始截内容。
- 24从 # 后面照长度 1 截取,截出的 "f" 就是一个完整原词(绿色这片)。把它加进结果列表。
- 25扫到末尾,整条字符串拆成了 ["abc", "de", "f"],和编码前一模一样。靠长度切片,原文里就算有 # 也不会拆错。
⚠️ 容易写错的地方
✗ 错:只用逗号/# 直接分隔词
✓ 对:用「长度 + # + 内容」
原文里若本来就有逗号或 #,按符号切会把一个词拆成两半,长度前缀法靠数字精确切片不受影响
✗ 错:解码时把长度数字当成一位数读
✓ 对:读到 # 为止才是完整长度
词长可能是 10、123 等多位数,必须一直读到 # 才知道完整长度,否则切片长度错
✗ 错:截完内容后 i 只 +1
✓ 对:i = j + 1 + 长度,跳过整段
下一个词的长度数字紧跟在上一段内容之后,i 要直接跳到那里,否则会把内容字符误当长度
完整代码(Python / C++ / Java)
Python
def encode(strs):
return ''.join(f'{len(s)}#{s}' for s in strs)
def decode(s):
res, i = [], 0
while i < len(s):
j = s.index('#', i) # 找长度后的 #
size = int(s[i:j]) # # 前是长度
res.append(s[j+1:j+1+size])# 按长度截取
i = j + 1 + size # 跳到下一段
return resC++
string encode(vector<string>& strs){
string r;
for (auto& s : strs) r += to_string(s.size()) + "#" + s;
return r;
}
vector<string> decode(string s){
vector<string> res; int i = 0;
while (i < (int)s.size()) {
int j = s.find('#', i);
int size = stoi(s.substr(i, j - i));
res.push_back(s.substr(j + 1, size));
i = j + 1 + size;
}
return res;
}Java
String encode(List<String> strs){
StringBuilder r = new StringBuilder();
for (String s : strs) r.append(s.length()).append('#').append(s);
return r.toString();
}
List<String> decode(String s){
List<String> res = new ArrayList<>(); int i = 0;
while (i < s.length()) {
int j = s.indexOf('#', i);
int size = Integer.parseInt(s.substring(i, j));
res.add(s.substring(j + 1, j + 1 + size));
i = j + 1 + size;
}
return res;
}复杂度
时间
O(n)
n 是编码串总长度;编码和解码都只把字符顺扫一遍
空间
O(n)
结果字符串/列表的长度与输入总字符数同阶,无额外开销
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 字符串的编码与解码 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么不直接用逗号或空格分隔每个词?+
因为原字符串里可能本来就含逗号、空格甚至任意字符。按某个固定符号切会把含该符号的词拆错。长度前缀法靠数字精确切片,不依赖内容里没有某符号这个假设。
长度是多位数(比如 12#…)会出问题吗?+
不会。解码时从当前位置一直读数字直到遇见 #,读到的整段才是完整长度。只要约定「长度后紧跟一个 #」,多位长度也能正确解析。
空字符串怎么编码?+
长度为 0,编码成 0#。解码读到长度 0,跳过 # 后截取 0 个字符,得到空串,完全正确。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 字符串的编码与解码 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。