在问 · 算法题解

387. 字符串中的第一个唯一字符

简单栈与堆哈希表计数力扣原题 ↗

题目描述

输入一个字符串 s。找出在整个字符串里只出现过一次的字符中,位置最靠前的那个。

返回它的下标,下标从 0 开始。如果每个字符都至少出现两次,返回 -1。

示例 1
输入:s = "leetcode"
输出:0

解释:l 只出现一次,位于下标 0。

示例 2
输入:s = "loveleetcode"
输出:2

解释:l、o 都重复,v 是第一个只出现一次的字符,下标为 2。

示例 3
输入:s = "aabb"
输出:-1

解释:a 和 b 都出现两次。

数据范围

  • 1 <= s.length <= 10^5
  • s 只含小写英文字母

解题思路:哈希表计数

一个字符是否唯一取决于整个字符串,扫描到它时还不知道后面会不会再出现。所以分两遍:第一遍用哈希表统计每个字符的出现次数;第二遍按原顺序找第一个次数为 1 的字符。

为什么正确

第二遍扫描到下标 i 时,s[0..i-1] 中的字符次数都大于 1,所以遇到的第一个次数为 1 的字符就是答案。

复杂度分析

时间 O(n),空间 O(Σ),Σ 为字符集大小(26)。

易错点

不能只扫一遍就判断“之前没见过”,那只说明它是第一次出现,后面还可能重复。必须先统计完整次数。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def firstUniqChar(self, s: str) -> int:
        cnt = collections.Counter(s)
        for i, ch in enumerate(s):
            if cnt[ch] == 1:
                return i
        return -1

载入编辑器,自己跑一遍 →

其他解法

三种方法都是 O(n):计数法最简单,需要扫字符串两遍;存储下标把第二遍缩到字符集大小;队列法只扫一遍,并能在线回答。

解法 2:哈希表存储下标 时间 O(n),空间 O(Σ)

遍历字符串,哈希表记录每个字符第一次出现的下标;字符再次出现时把值改成 -1。遍历结束后,在哈希表中找出所有不为 -1 的值里最小的那个。没有这样的值就返回 -1。

第二次遍历只扫哈希表,规模是字符集大小 Σ 而不是 n,字符串很长时更快。代码比计数略复杂。

class Solution:
    def firstUniqChar(self, s: str) -> int:
        pos = {}
        for i, ch in enumerate(s):
            if ch in pos:
                pos[ch] = -1
            else:
                pos[ch] = i
        ans = len(s)
        for p in pos.values():
            if p != -1 and p < ans:
                ans = p
        return -1 if ans == len(s) else ans

解法 3:队列延迟删除 时间 O(n),空间 O(Σ)

哈希表记录字符第一次出现的下标,重复出现时标记为 -1。第一次出现的字符和下标入队。每读入一个字符后,不断弹出队首中已被标记为 -1 的字符。遍历结束后队首就是第一个唯一字符,队列为空则返回 -1。

只需扫描字符串一次,并且在任意时刻都能给出当前前缀的答案,适合数据流场景。需要额外的队列,代码最长。

from collections import deque


class Solution:
    def firstUniqChar(self, s: str) -> int:
        pos = {}
        q = deque()
        for i, ch in enumerate(s):
            if ch not in pos:
                pos[ch] = i
                q.append((ch, i))
            else:
                pos[ch] = -1
                while q and pos[q[0][0]] == -1:
                    q.popleft()
        return q[0][1] if q else -1

栈与堆 专题的其他题目

查看 栈与堆 全部题目 →