题目描述

给定单词序列 words 和正整数 n,返回恰好出现 n 次的单词中,首次出现位置最靠后的那个。

单词区分大小写;不存在符合条件的单词时,返回空字符串。

示例 1:

输入: words = ["python","Java","cpp","go","java","go","cpp"], n = 2
输出: "go"
解释: cpp、go 都出现两次,go 首次出现在下标 3,晚于 cpp 的下标 2。Java 与 java 大小写不同。

示例 2:

输入: words = ["cpp","go","go","cpp"], n = 2
输出: "go"
解释: 两词都出现两次,但 go 的首次位置更靠后;如果按最后出现位置选择,反而会得到 cpp。

提示:

  • 本篇选用如下平局规则:“靠后”比较第一次出现的位置。
  • 相同单词后续重复的位置不改变其优先级。

题意分析

频次必须恰好为 n,扫描途中出现过 n 次不代表最终仍合格。先得到完整频次,再按首次出现位置选最靠后的词,能避免把最后出现位置或字典序误当排序依据。

解法:频次筛选与首次位置扫描

核心思路

[!blue]

第一遍用 counts 统计每个单词的总次数。第二遍从左到右扫描,只有 seen 尚未记录的词才处理,随后立即标记,使该词的所有后续重复位置都不能影响优先级。

遇到总次数等于 n 的首次出现时覆盖答案。因为这些首次位置按递增顺序被处理,每次覆盖都比此前候选更靠后,扫描结束的最后一次覆盖就是目标。

哈希表按原字符串区分大小写,不进行转换。没有合格词时始终保留初始空串,且无需对全部不同单词额外排序。

解题步骤

  1. 先统计全部单词总次数。
  2. 再次从左到右扫描,用集合只处理每个词的第一次出现。
  3. 频次符合时覆盖答案,最后一次覆盖即首次位置最靠后者。

代码实现

class Solution {
    public String latestFirstWord(String[] words, int n) {
        Map<String, Integer> counts = new HashMap<>();

        for (String word : words) {
            counts.merge(word, 1, Integer::sum);
        }

        Set<String> seen = new HashSet<>();
        String out = "";

        for (String word : words) {
            if (seen.add(word) && counts.get(word) == n) {
                out = word;
            }
        }

        return out;
    }
}
func latestFirstWord(words []string, n int) string {
    counts := map[string]int{}
    for _, word := range words {
        counts[word]++
    }
    seen := map[string]bool{}
    out := ""
    for _, word := range words {
        if !seen[word] {
            seen[word] = true
            if counts[word] == n {
                out = word
            }
        }
    }
    return out
}

复杂度分析

  • 时间复杂度:期望 $O(C)$。
  • 空间复杂度:额外空间 $O(C)$,C 为输入单词总字符数。

关键点总结

[!green]

必须先得到完整频次,再按首次位置选择;最后一次出现的位置不能替代首次位置。

易错点总结

[!yellow]

如果误用最后出现位置,给定示例会选 cpp,得到与示例不符的结果。

相似题目

题目 难度 关联与区别
387. 字符串中的第一个唯一字符 简单 同样先统计频次再按位置选择,原题找最早的唯一字符,本题找指定频次中首次位置最靠后者。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/2648255690
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!