2026华为OD面试题020:日志文件异常检测 题目描述某系统的日志监控服务需要实时检测日志里的异常模式。系统每天产生大量日志每条记录包含一个字符串标识。异常模式的定义是同一时间段内至少出现 3 次的日志标识。请写一个程序找出所有出现次数大于等于 3 次的日志标识按下面规则输出先按出现次数从高到低排序出现次数相同按首次出现的先后顺序排序输入描述所有日志标识以空格分割。标识由大小写字母、数字和下划线组成长度不超过 50。日志数量范围[1, 10000]。输出描述输出所有出现次数大于等于 3 次的日志标识按上述规则排序以空格分割。如果没有符合条件的输出NONE。用例 1输入error_404 error_404 warning_500 error_404 info_200 warning_500 warning_500 info_200输出error_404 warning_500说明error_404出现 3 次第 0、1、3 个首次出现在第 0 个warning_500出现 3 次第 2、5、6 个首次出现在第 2 个info_200只出现 2 次第 4、7 个不符合条件error_404和warning_500次数相同按首次出现先后先输出error_404再输出warning_500原题文件在说明末尾截断这里按题目描述和示例补全。题目里同一时间段内按整个输入窗口理解从示例看就是对全部输入做计数。讲个故事值班室的告警轰炸小张是运维凌晨三点被电话叫醒告警群炸了。几百条日志刷屏他眼睛都看花。同一类报错反复出现有的是偶发一下有的是疯狂刷屏。组长让他把短时间冒出来 3 次以上的日志标识挑出来按出现次数从多到少列个清单好优先排查最凶的那几个。小张抓了下头这不就是数词频吗数完挑出大于等于 3 次的再排个序次数一样就比谁先登场。核心原理哈希计数加双关键字排序这题本质是词频统计。扫一遍日志每碰到一个标识就做两件事在哈希表里把它的计数加 1如果是第一次见顺便记下它最早出现在第几个位置。扫完之后把计数大于等于 3 的挑出来按两个关键字排序第一关键字出现次数从高到低第二关键字首次出现位置从早到晚次数相同就比谁先出场先出场的排前面。要是挑完一个都没有就输出NONE。说白了记 count 是基本功真正考人的是那个第二关键字。很多人排序只按次数排忘了次数相同还得看首次出现顺序用例就过不去。怎么实现读入所有标识按空白符切分成数组边扫边用哈希表记count和firstIdx第一次见就登记位置遍历哈希表筛出count 3的按count降序、firstIdx升序排序结果为空输出NONE否则空格拼接输出读入用按 token 读的方式单行多行都能兜住更稳。代码实现C 语言#includestdio.h#includestdlib.h#includestring.h#defineHASHSIZE20011typedefstructNode{charkey[64];intcount;intfirstIdx;structNode*next;}Node;Node*table[HASHSIZE];unsignedinthashStr(constchar*s){unsignedinth0;for(;*s;s)hh*131u(unsignedchar)*s;returnh%HASHSIZE;}Node*findOrInsert(constchar*key,intidx){unsignedinthhashStr(key);Node*ptable[h];while(p){if(strcmp(p-key,key)0)returnp;pp-next;}p(Node*)malloc(sizeof(Node));strcpy(p-key,key);p-count0;p-firstIdxidx;p-nexttable[h];table[h]p;returnp;}typedefstruct{charkey[64];intcount;intfirstIdx;}Item;staticItem items[10005];intcmp(constvoid*a,constvoid*b){constItem*xa,*yb;if(x-count!y-count)returny-count-x-count;// 次数降序returnx-firstIdx-y-firstIdx;// 首次出现升序}intmain(){chartok[64];intidx0;while(scanf(%63s,tok)1){Node*pfindOrInsert(tok,idx);p-count;idx;}intm0;for(inti0;iHASHSIZE;i){for(Node*ptable[i];p;pp-next){if(p-count3){strcpy(items[m].key,p-key);items[m].countp-count;items[m].firstIdxp-firstIdx;m;}}}if(m0){printf(NONE\n);}else{qsort(items,m,sizeof(Item),cmp);for(inti0;im;i){if(i)printf( );printf(%s,items[i].key);}printf(\n);}return0;}C#includebits/stdc.husingnamespacestd;intmain(){unordered_mapstring,pairint,intmp;// {count, firstIdx}string tok;intidx0;while(cintok){if(mp.find(tok)mp.end())mp[tok]{0,idx};mp[tok].first;idx;}vectortupleint,int,stringv;for(autop:mp){if(p.second.first3)v.push_back(make_tuple(p.second.first,p.second.second,p.first));}if(v.empty()){coutNONEendl;}else{sort(v.begin(),v.end(),[](constautoa,constautob){if(get0(a)!get0(b))returnget0(a)get0(b);// 次数降序returnget1(a)get1(b);// 首次出现升序});for(size_t i0;iv.size();i){if(i)cout ;coutget2(v[i]);}coutendl;}return0;}Javaimportjava.util.*;publicclassMain{publicstaticvoidmain(String[]args){ScannerscnewScanner(System.in);MapString,int[]mpnewHashMap();// [count, firstIdx]intidx0;while(sc.hasNext()){Stringtoksc.next();if(!mp.containsKey(tok))mp.put(tok,newint[]{0,idx});mp.get(tok)[0];idx;}ListStringkeysnewArrayList();for(Map.EntryString,int[]e:mp.entrySet()){if(e.getValue()[0]3)keys.add(e.getKey());}if(keys.isEmpty()){System.out.println(NONE);}else{keys.sort((a,b)-{intcamp.get(a)[0],cbmp.get(b)[0];if(ca!cb)returncb-ca;// 次数降序returnmp.get(a)[1]-mp.get(b)[1];// 首次出现升序});StringBuildersbnewStringBuilder();for(inti0;ikeys.size();i){if(i0)sb.append( );sb.append(keys.get(i));}System.out.println(sb);}}}JavaScriptconstreadlinerequire(readline);constrlreadline.createInterface({input:process.stdin});letbuf[];rl.on(line,(line)buf.push(line));rl.on(close,(){consttoksbuf.join( ).split(/\s/).filter(tt.length0);constmpnewMap();// key - {count, firstIdx}letidx0;for(consttoftoks){if(!mp.has(t))mp.set(t,{count:0,firstIdx:idx});mp.get(t).count;idx;}constarr[];for(const[k,v]ofmp){if(v.count3)arr.push({key:k,count:v.count,firstIdx:v.firstIdx});}if(arr.length0){console.log(NONE);}else{arr.sort((a,b){if(a.count!b.count)returnb.count-a.count;// 次数降序returna.firstIdx-b.firstIdx;// 首次出现升序});console.log(arr.map(xx.key).join( ));}});Pythonimportsysdefmain():tokssys.stdin.read().split()mp{}# key - [count, firstIdx]foridx,tinenumerate(toks):iftnotinmp:mp[t][0,idx]mp[t][0]1# 筛出次数 3 的按次数降序、首次出现升序排arr[(v[0],v[1],k)fork,vinmp.items()ifv[0]3]ifnotarr:print(NONE)else:arr.sort(keylambdax:(-x[0],x[1]))print( .join(x[2]forxinarr))main()复杂度分析扫描计数O(n)n 是日志条数最多 10000筛选O(m)m 是不同标识数排序O(m * log m)总时间复杂度O(n m * log m)空间复杂度O(m)n 最大 10000哈希表加排序轻松跑完毫无压力。总结一下日志异常检测 词频统计 双关键字排序。套路记牢哈希表一边计数一边记首次出现位置筛count 3按 count 降序、firstIdx 升序排没结果输出NONE最容易被坑的是第二关键字。只按次数排次数相同的几个顺序就乱了用例直接挂。顺手记个firstIdx当 tiebreaker稳。词频统计是 OD 的常客类似的还有热点网站统计、API 集群负载统计套路都是这一套换个壳子而已。你做词频类的题习惯用 map 还是手搓哈希表欢迎在评论区聊聊。