替换字符串中的括号内容 图解题解
这道题到底在问什么
- 输入
- s = "(name)is(age)yearsold", knowledge = [["name","bob"],["age","two"]]
- 输出
- "bobistwoyearsold"(name 换 bob、age 换 two,中间字母不动)
- 输入
- s = "hi(name)", knowledge = [["a","b"]]
- 输出
- "hi?"(清单里没有 name,这对括号换成问号)
最优解:一步一步想明白
- 3记住这套动作:先建表,再一遍扫。括号外的字母照抄,括号内的字符拼成键去查表,查到用值替换、查不到用问号替换。下面每一帧都在套这个流程,先看建表,再看指针一格一格往右走。
- 4先看清这串 "z(a)(a)(bc)" 的结构。开头的 z 在括号外,是要原样保留的普通字母;后面是三对括号,里面的键分别是 a、a、bc。绿色标出的是括号内的键、灰色是括号本身。心里有了这张地图,再开始建表和扫描。
- 5正式扫描前,先把 knowledge 这张清单搬进哈希表。现在表还空着,一条都没有。建表的好处是,后面不管查多少次键,每次都是常数时间,不用每次都在原始清单里从头找一遍。
- 6knowledge 的第一条是键 a 对应值 go,放进表里。以后只要在括号里读到键 a,一查表就能拿到 go。
- 7第二条是键 k 对应值 hi,也放进表里。注意这条 k 在待处理的字符串里其实一次都用不上,但表里存着不碍事,查不到才是问题,查得到用不到没关系。
- 8表建好了,一共两条。现在把指针放回字符串开头,准备从下标 0 开始一格一格往右扫。结果串目前是空的,接下来会一点点拼出来。
- 9指针在下标 0,这是括号外面的普通字母 "z"。不用查表,直接原样抄进结果。结果串现在是 "z"。
- 10指针走到下标 1,这是一个左括号。看到左括号就知道:接下来一段是括号里的键。先把键的缓冲清空,从下一格开始一个字符一个字符地把键收进来,直到撞见右括号为止。
- 11指针在括号里,读到字符 "a",把它接到键的缓冲上,现在收集到的键是 "a"。还没到右括号,继续往右收,看键会不会更长。
- 12指针走到下标 3 的右括号,这对括号闭合了。中间收集到的键是 "a"。现在拿这个键去知识表里查一下,看看表里有没有这一行。
- 13表里正好有键 "a" 这一行,对应的值是 "go"。于是把左括号、键、右括号这一整对,统统换成 "go" 接到结果后面。现在结果串变成 "zgo"。
- 14指针走到下标 4,这是一个左括号。看到左括号就知道:接下来一段是括号里的键。先把键的缓冲清空,从下一格开始一个字符一个字符地把键收进来,直到撞见右括号为止。
- 15指针在括号里,读到字符 "a",把它接到键的缓冲上,现在收集到的键是 "a"。还没到右括号,继续往右收,看键会不会更长。
- 16指针走到下标 6 的右括号,这对括号闭合了。中间收集到的键是 "a"。现在拿这个键去知识表里查一下,看看表里有没有这一行。
- 17表里正好有键 "a" 这一行,对应的值是 "go"。于是把左括号、键、右括号这一整对,统统换成 "go" 接到结果后面。现在结果串变成 "zgogo"。
- 18指针走到下标 7,这是一个左括号。看到左括号就知道:接下来一段是括号里的键。先把键的缓冲清空,从下一格开始一个字符一个字符地把键收进来,直到撞见右括号为止。
- 19指针在括号里,读到字符 "b",把它接到键的缓冲上,现在收集到的键是 "b"。还没到右括号,继续往右收,看键会不会更长。
- 20指针在括号里,读到字符 "c",把它接到键的缓冲上,现在收集到的键是 "bc"。还没到右括号,继续往右收,看键会不会更长。
- 21指针走到下标 10 的右括号,这对括号闭合了。中间收集到的键是 "bc"。现在拿这个键去知识表里查一下,看看表里有没有这一行。
- 22拿这个键到哈希表里查一次,发现没有 "bc" 这个键。按规则,查不到的键要用一个问号来替换,而不是留空或保留原样。于是这对括号换成 "?" 接到结果后面,结果串变成 "zgogo?"。
- 23指针走到字符串末尾,整个 s 只扫了一遍,每个字符都处理过了。括号外的字母原样抄,括号内的键查表替换,结果串一路拼到现在是 "zgogo?"。
- 24回放一遍怎么拼出来的:开头的 z 在括号外,原样保留;第一对 (a) 查到 go、第二对 (a) 同一个键还是查到 go,同一个键出现两次都替换;最后 (bc) 在表里查不到,换成一个问号。连起来正好是 "zgogo?"。
- 25最后对照一下规则有没有走偏:括号外的字母原样保留、括号内的键查到就用值替换、查不到就用一个问号替换,同一个键出现几次就替换几次。演示这三种情况都碰上了,答案 "zgogo?" 完全符合规则。
⚠️ 容易写错的地方
✗ 错:查不到的键随手留空,或者把原来的键原样保留
✓ 对:查不到必须替换成一个问号 "?",既不是留空也不是保留原键
题目把「未知键」的处理规定得很死:整对括号换成一个问号。留空会漏字符,保留原键会把括号或键名混进结果,都是错的
✗ 错:把括号外的字母也拿去查表替换
✓ 对:只有括号里的键才替换,括号外的字母一律原样抄进结果
例子 "(a)(a)(a)aaa" 里,前面括号中的 a 要换成值,后面裸露的三个 a 不在括号里,必须原样保留。把括号外字母也替换会得到完全不同的错误串
✗ 错:以为同一个键只替换第一次出现,或者假设键只有一个字符
✓ 对:每对括号都独立查表替换,同一个键出现多少次就替换多少次;键可能是多字符,要整段收集
键在 s 里可以重复出现,像两对 (a) 都要各换一次;键也可能是 "name" 这种多字符,只取一个字符当键会查错。必须把括号里整段收全再查
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def evaluate(self, s: str, knowledge: List[List[str]]) -> str:
d = {a: b for a, b in knowledge}
i, n = 0, len(s)
ans = []
while i < n:
if s[i] == '(':
j = s.find(')', i + 1)
ans.append(d.get(s[i + 1 : j], '?'))
i = j
else:
ans.append(s[i])
i += 1
return ''.join(ans)C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
string evaluate(string s, vector<vector<string>>& knowledge) {
unordered_map<string, string> d;
for (auto& e : knowledge) {
d[e[0]] = e[1];
}
string ans;
for (int i = 0; i < s.size(); ++i) {
if (s[i] == '(') {
int j = s.find(")", i + 1);
auto t = s.substr(i + 1, j - i - 1);
ans += d.count(t) ? d[t] : "?";
i = j;
} else {
ans += s[i];
}
}
return ans;
}
};Java
import java.util.*;
class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(){} TreeNode(int val){this.val=val;} TreeNode(int val, TreeNode left, TreeNode right){this.val=val;this.left=left;this.right=right;} }
class ListNode { int val; ListNode next; ListNode(){} ListNode(int val){this.val=val;} ListNode(int val, ListNode next){this.val=val;this.next=next;} }
class Solution {
public String evaluate(String s, List<List<String>> knowledge) {
Map<String, String> d = new HashMap<>(knowledge.size());
for (List<String> e : knowledge) {
d.put(e.get(0), e.get(1));
}
StringBuilder ans = new StringBuilder();
for (int i = 0; i < s.length(); ++i) {
if (s.charAt(i) == '(') {
int j = s.indexOf(')', i + 1);
ans.append(d.getOrDefault(s.substring(i + 1, j), "?"));
i = j;
} else {
ans.append(s.charAt(i));
}
}
return ans.toString();
}
}复杂度
时间
O(n + m)
n 是 s 的长度,m 是 knowledge 里所有键和值的总字符数。建表把 m 个字符过一遍;扫描时每个字符只经过常数次(找右括号是不重叠地往后走,整趟合起来仍是 O(n)),查表平均常数时间。两段相加是 O(n + m)
空间
O(n + m)
峰值占用两块:哈希表存下所有键值,是 O(m);拼结果的容器最终和输出等长,是 O(n)。两者都随输入变大而变大,峰值取二者之和 O(n + m),不是常数
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 替换字符串中的括号内容 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么一遍扫描就够,不用反复回头?+
因为题目保证括号不嵌套,而且每个左括号都有对应的右括号。所以从左往右扫,遇到左括号就一路往右收键直到右括号,这段括号一次性处理完,指针直接跳到右括号后面继续,不会再回头。每个字符至多被读常数次,整趟就是线性的一遍扫描。
知识表为什么用哈希表,直接在原始清单里线性找不行吗?+
哈希表把「按键查值」做成平均常数时间。如果每遇到一个键就在原始 knowledge 里从头线性找,单次是 O(m) 量级,s 里若有很多对括号,总代价会退化成括号数乘以清单长度,明显更慢。先花 O(m) 建一次表,之后每次查都是常数,整体更划算。
如果题目改成允许嵌套括号,思路要怎么调整?+
一遍平扫就不够了,因为里层括号要先算出来再拼给外层。通用做法是用一个栈:遇到左括号压栈、开一段新的收集区,遇到右括号就弹栈,把里层已经替换好的结果并到外层去,或者干脆写成递归,从最内层往外层逐层求值。本题因为明确说了不嵌套,才可以用最简单的一遍扫描。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 替换字符串中的括号内容 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。