子域名访问计数 图解题解
这道题到底在问什么
- 输入
- cpdomains = ["900 g.mail.com","50 yahoo.com","1 intel.mail.com","5 wiki.org"]
- 输出
- ["901 mail.com","50 yahoo.com","900 g.mail.com","5 wiki.org","5 org","1 intel.mail.com","951 com"](顺序不限)
最优解:一步一步想明白
- 3两步循环:外层逐条记录、内层逐级后缀。每个后缀都把这条记录的次数加上去,靠哈希表自动把相同域名的次数累加在一起。
- 4count = {}(空)开始前:哈希表 count 为空,还没处理任何记录。
- 5cnt = 900 ; d = g.mail.com取第 1 条记录 "900 g.mail.com":次数 cnt=900,域名 d=g.mail.com。
- 6后缀 = [g.mail.com , mail.com , com]把 g.mail.com 按点号切成各级后缀:g.mail.com / mail.com / com。每一个都要加上 900。
- 7count["g.mail.com"] : 0 + 900 = 900给后缀 g.mail.com 加上 900:原来 0 → 现在 900(第一次出现,新建桶)。
- 8count["mail.com"] : 0 + 900 = 900给后缀 mail.com 加上 900:原来 0 → 现在 900(第一次出现,新建桶)。
- 9count["com"] : 0 + 900 = 900给后缀 com 加上 900:原来 0 → 现在 900(第一次出现,新建桶)。
- 10cnt = 50 ; d = yahoo.com取第 2 条记录 "50 yahoo.com":次数 cnt=50,域名 d=yahoo.com。
- 11后缀 = [yahoo.com , com]把 yahoo.com 按点号切成各级后缀:yahoo.com / com。每一个都要加上 50。
- 12count["yahoo.com"] : 0 + 50 = 50给后缀 yahoo.com 加上 50:原来 0 → 现在 50(第一次出现,新建桶)。
- 13count["com"] : 900 + 50 = 950给后缀 com 加上 50:原来 900 → 现在 950(已存在,累加)。
- 14cnt = 1 ; d = intel.mail.com取第 3 条记录 "1 intel.mail.com":次数 cnt=1,域名 d=intel.mail.com。
- 15后缀 = [intel.mail.com , mail.com , com]把 intel.mail.com 按点号切成各级后缀:intel.mail.com / mail.com / com。每一个都要加上 1。
- 16count["intel.mail.com"] : 0 + 1 = 1给后缀 intel.mail.com 加上 1:原来 0 → 现在 1(第一次出现,新建桶)。
- 17count["mail.com"] : 900 + 1 = 901给后缀 mail.com 加上 1:原来 900 → 现在 901(已存在,累加)。
- 18count["com"] : 950 + 1 = 951给后缀 com 加上 1:原来 950 → 现在 951(已存在,累加)。
- 19cnt = 5 ; d = wiki.org取第 4 条记录 "5 wiki.org":次数 cnt=5,域名 d=wiki.org。
- 20后缀 = [wiki.org , org]把 wiki.org 按点号切成各级后缀:wiki.org / org。每一个都要加上 5。
- 21count["wiki.org"] : 0 + 5 = 5给后缀 wiki.org 加上 5:原来 0 → 现在 5(第一次出现,新建桶)。
- 22count["org"] : 0 + 5 = 5给后缀 org 加上 5:原来 0 → 现在 5(第一次出现,新建桶)。
- 23扫描结束 ; 共 7 个域名所有记录处理完。哈希表里每个域名对应的就是它的总访问次数,把它们组成结果即可。
⚠️ 容易写错的地方
✗ 错:只统计完整域名,忘了父域名
✓ 对:对每一级后缀都累加
访问 g.mail.com 也算访问了 mail.com 和 com,漏掉父域会少计
✗ 错:把次数当字符串直接相加
✓ 对:先 int() 转成整数再累加
'900'+'50' 会拼成 '90050',必须转成数字做加法
✗ 错:用 count[key] += cnt 但没处理 key 不存在
✓ 对:用 defaultdict / merge / getOrDefault
普通字典里键不存在会报 KeyError,第一次出现要能从 0 起累加
完整代码(Python / C++ / Java)
Python
def subdomainVisits(cpdomains):
from collections import defaultdict
count = defaultdict(int)
for cp in cpdomains:
cnt_s, dom = cp.split() # 拆出次数和域名
cnt = int(cnt_s)
parts = dom.split('.')
for i in range(len(parts)): # 逐级后缀
suffix = '.'.join(parts[i:])
count[suffix] += cnt
return [f'{c} {d}' for d, c in count.items()]C++
vector<string> subdomainVisits(vector<string>& cpdomains){
unordered_map<string,int> count;
for (auto& cp : cpdomains) {
int sp = cp.find(' ');
int cnt = stoi(cp.substr(0, sp));
string dom = cp.substr(sp + 1);
for (int i = 0; i < dom.size(); i++)
if (i == 0 || dom[i-1] == '.')
count[dom.substr(i)] += cnt; // 每个后缀
}
vector<string> res;
for (auto& [d, c] : count) res.push_back(to_string(c)+" "+d);
return res;
}Java
public List<String> subdomainVisits(String[] cpdomains) {
Map<String,Integer> count = new HashMap<>();
for (String cp : cpdomains) {
int sp = cp.indexOf(' ');
int cnt = Integer.parseInt(cp.substring(0, sp));
String dom = cp.substring(sp + 1);
for (int i = 0; i < dom.length(); i++)
if (i == 0 || dom.charAt(i-1) == '.')
count.merge(dom.substring(i), cnt, Integer::sum);
}
List<String> res = new ArrayList<>();
count.forEach((d, c) -> res.add(c + " " + d));
return res;
}复杂度
时间
O(N·L)
N 条记录、每条域名长度 L;切后缀并累加都与 L 成正比
空间
O(N·L)
哈希表里最多存下所有出现过的域名(含各级父域)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 子域名访问计数 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么要统计父域名?+
题目规定访问一个子域名也算访问了它的所有上级域名。所以 g.mail.com 的访问量必须同时计入 mail.com 和 com。
怎么从 g.mail.com 切出所有父域?+
按 '.' 切成 [g, mail, com],然后取后缀:从下标 0 起是 g.mail.com,从 1 起是 mail.com,从 2 起是 com。逐级去掉最左段即可。
为什么选哈希表而不是排序?+
我们要按域名把次数累加在一起,哈希表的「键 → 值累加」天然适合,平均 O(1) 更新,不需要排序。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 子域名访问计数 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。