题目描述

给你一个只包含英文字母和空格的字符串 s,请删除重复的单词,只保留每个单词第一次出现时的相对顺序。

返回用单个空格连接的结果,不保留首尾空格或连续空格。单词比较区分大小写;空串或全空格输入返回空字符串。

示例 1:

输入: s = " java go java python go "
输出: "java go python"
解释: 第一次出现顺序为 java、go、python,后续重复项删除。

示例 2:

输入: s = "Java java Java"
输出: "Java java"
解释: Java 与 java 视为两个不同单词。

提示:

  • 大小写敏感。
  • 空串或全空格输入返回空字符串。

题意分析

需要保留首次出现的单词,而不是删除所有出现重复的词。判重只关心此前是否出现,输出顺序则必须跟随从左到右的扫描顺序,不能直接遍历无序集合生成结果。

解法:集合判重与顺序输出

核心思路

[!blue]

将连续空格作为分隔,逐个取得非空单词。seen 保存已经输出的单词,out 按首次出现顺序保存结果;若当前词不在集合中,就同时加入两者,否则跳过。

扫描任意前缀后,out 恰好包含该前缀所有不同单词的首次出现,因此扫描结束便满足去重与保序要求。比较时不转换大小写,让 Java 与 java 保持不同。

最后用单个空格连接 out,自然消除首尾和连续空格。Java 显式跳过空词,Go 的 Fields 不产生空词,空串及全空格输入都返回空字符串。

解题步骤

  1. 按题目约定分词,并跳过空词。
  2. 集合只用于判重,首次见到的词追加到结果。
  3. 用单个空格连接结果。

代码实现

class Solution {
    public String uniqueWords(String s) {
        Set<String> seen = new HashSet<>();
        List<String> out = new ArrayList<>();

        for (String word : s.trim().split(" +")) {
            if (!word.isEmpty() && seen.add(word)) {
                out.add(word);
            }
        }

        return String.join(" ", out);
    }
}
import "strings"

func uniqueWords(s string) string {
    seen := map[string]bool{}
    out := []string{}
    for _, word := range strings.Fields(s) {
        if !seen[word] {
            seen[word] = true
            out = append(out, word)
        }
    }
    return strings.Join(out, " ")
}

复杂度分析

  • 时间复杂度:期望 $O(n)$。
  • 空间复杂度:额外空间 $O(n)$,n 为字符串长度。

关键点总结

[!green]

去重与输出顺序是两个职责:集合负责是否出现过,扫描和追加顺序负责稳定性。

易错点总结

[!yellow]

不按字典序重排,也不删除所有重复词;第一次出现的那一份需要保留。

相似题目

题目 难度 关联与区别
387. 字符串中的第一个唯一字符 简单 两题都利用原出现顺序;387 需先统计完整频次,筛选只出现一次的字符,本题仅用已见集合,保留每种单词首次出现,不要求总次数为一。
217. 存在重复元素 简单 两题都用已见集合判断当前值是否重复;本题对首次出现的单词追加输出,该题首次发现重复即可结束。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/2401105392
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!