实时社交媒体热点追踪

题意

设计一个社交媒体热词追踪系统。系统维护一个带计数器的队列和一个容量为 的展示面板。

第一行输入整数 (面板容量),之后每行输入一批空格分隔的关键词。对于每一批:

  1. 如果关键词已在队列中,计数器 +1(位置不变)
  2. 如果是新关键词,追加到队尾,计数器初始化为 1
  3. 处理完本批后,从队头取出最多 个关键词并输出(格式:keyword count 对,空格分隔),然后将它们从队列中移除
  4. 如果队列为空(没有可输出的),输出 null
  5. 被移除的关键词如果后续再出现,视为全新热词

注意:空行也算一批输入,此时不添加任何关键词,直接进入输出阶段。

思路

这题乍一看信息量不小,但拆解下来核心操作就两个:按序存储 + 快速查找

先想想需要什么数据结构?

队列要保持插入顺序(FIFO),同时还要能快速判断某个关键词是否已经存在——如果存在就更新计数,不存在就追加到尾部。

普通数组或链表可以保序,但查找是 的。哈希表查找是 ,但不保序。怎么兼顾?

答案是有序哈希表(或者链表 + 哈希表的组合):

  • 用链表维护插入顺序
  • 用哈希表记录每个关键词在链表中的位置,实现 查找和更新

在不同语言中,这个结构有现成的实现:

  • C++:std::list + unordered_map(手动组合)
  • Java:LinkedHashMap(天然有序)
  • Python:OrderedDict(天然有序,popitem(last=False) 弹出队头)
  • JavaScript:Map(ES6 规范保证插入顺序)

每一批处理流程:

  1. 遍历本批关键词,存在则计数 +1,不存在则插入队尾
  2. 从队头弹出 个元素,拼接输出
  3. 队列为空则输出 null

整体时间复杂度 ,其中 是批次数, 是每批关键词数。由于 均不超过 100,完全不会有性能问题。

有一个容易踩的坑:输入中的空行也是合法的一批,需要正常执行输出逻辑(此时不添加关键词,如果队列已空就输出 null)。

代码

#include <bits/stdc++.h>
using namespace std;

int main(){
    int k;
    string line;
    getline(cin, line);
    k = stoi(line);

    list<pair<string,int>> q;
    unordered_map<string, list<pair<string,int>>::iterator> mp;

    while(getline(cin, line)){
        istringstream iss(line);
        string word;
        while(iss >> word){
            auto it = mp.find(word);
            if(it != mp.end()){
                it->second->second++;
            } else {
                q.push_back({word, 1});
                mp[word] = prev(q.end());
            }
        }

        int cnt = min(k, (int)q.size());
        if(cnt == 0){
            printf("null\n");
        } else {
            for(int i = 0; i < cnt; i++){
                if(i) printf(" ");
                printf("%s %d", q.front().first.c_str(), q.front().second);
                mp.erase(q.front().first);
                q.pop_front();
            }
            printf("\n");
        }
    }
    return 0;
}
import java.util.*;
import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int k = Integer.parseInt(br.readLine().trim());

        LinkedHashMap<String, Integer> q = new LinkedHashMap<>();
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
            line = line.trim();
            if (!line.isEmpty()) {
                String[] words = line.split("\\s+");
                for (String w : words) {
                    if (w.isEmpty()) continue;
                    q.put(w, q.getOrDefault(w, 0) + 1);
                }
            }

            int cnt = Math.min(k, q.size());
            if (cnt == 0) {
                sb.append("null\n");
            } else {
                Iterator<Map.Entry<String, Integer>> it = q.entrySet().iterator();
                for (int i = 0; i < cnt; i++) {
                    Map.Entry<String, Integer> e = it.next();
                    if (i > 0) sb.append(' ');
                    sb.append(e.getKey()).append(' ').append(e.getValue());
                    it.remove();
                }
                sb.append('\n');
            }
        }
        System.out.print(sb);
    }
}
import sys
from collections import OrderedDict

def main():
    data = sys.stdin.read()
    if data.endswith('\n'):
        data = data[:-1]
    input_data = data.split('\n')
    idx = 0
    k = int(input_data[idx]); idx += 1

    q = OrderedDict()
    out = []

    while idx < len(input_data):
        line = input_data[idx].strip(); idx += 1
        if line:
            words = line.split()
            for w in words:
                if w in q:
                    q[w] += 1
                else:
                    q[w] = 1

        cnt = min(k, len(q))
        if cnt == 0:
            out.append("null")
        else:
            parts = []
            for _ in range(cnt):
                key, val = q.popitem(last=False)
                parts.append(f"{key} {val}")
            out.append(' '.join(parts))

    print('\n'.join(out))

main()
process.stdin.resume();
process.stdin.setEncoding('utf8');
let input = '';
process.stdin.on('data', c => input += c);
process.stdin.on('end', () => {
    if (input.endsWith('\n')) input = input.slice(0, -1);
    const lines = input.split('\n');
    let idx = 0;
    const k = parseInt(lines[idx++]);

    const q = new Map();
    const out = [];

    while (idx < lines.length) {
        const line = lines[idx++].trim();
        if (line) {
            const words = line.split(/\s+/);
            for (const w of words) {
                if (!w) continue;
                q.set(w, (q.get(w) || 0) + 1);
            }
        }

        const cnt = Math.min(k, q.size);
        if (cnt === 0) {
            out.push("null");
        } else {
            const parts = [];
            let i = 0;
            const toDelete = [];
            for (const [key, val] of q) {
                if (i >= cnt) break;
                parts.push(key + ' ' + val);
                toDelete.push(key);
                i++;
            }
            for (const key of toDelete) {
                q.delete(key);
            }
            out.push(parts.join(' '));
        }
    }

    console.log(out.join('\n'));
});