LeetCode 387简单字符串
字符串中的第一个唯一字符 图解题解
这道题到底在问什么
给定一个字符串 s ,找到它第一个不重复的字符,并返回它的下标。如果不存在不重复的字符,返回 -1。
- s
- "aabbcde"
- 输出
- 4(字符 'c')
最优解:一步一步想明白
- 3记住两趟的分工:第一趟只管「数清楚每个字母出现几次」,第二趟只管「按原顺序找第一个出现 1 次的」。下面每一帧都在套这两趟。
- 4第一趟:从头扫一遍,把每个字符出现的次数都记进右边的计数表 cnt。现在 cnt 是空的。
- 5第一趟扫到下标 0,这一格是 'a'(橙色高亮)。先看清是哪个字符,下一步把它的次数加一。
- 6把 'a' 的次数加一,它现在出现了 1 次。第一趟走完,cnt 就记下了每个字母各出现几次。
- 7第一趟扫到下标 1,这一格是 'a'(橙色高亮)。先看清是哪个字符,下一步把它的次数加一。
- 8把 'a' 的次数加一,它现在出现了 2 次。第一趟走完,cnt 就记下了每个字母各出现几次。
- 9第一趟扫到下标 2,这一格是 'b'(橙色高亮)。先看清是哪个字符,下一步把它的次数加一。
- 10把 'b' 的次数加一,它现在出现了 1 次。第一趟走完,cnt 就记下了每个字母各出现几次。
- 11第一趟扫到下标 3,这一格是 'b'(橙色高亮)。先看清是哪个字符,下一步把它的次数加一。
- 12把 'b' 的次数加一,它现在出现了 2 次。第一趟走完,cnt 就记下了每个字母各出现几次。
- 13第一趟扫到下标 4,这一格是 'c'(橙色高亮)。先看清是哪个字符,下一步把它的次数加一。
- 14把 'c' 的次数加一,它现在出现了 1 次。第一趟走完,cnt 就记下了每个字母各出现几次。
- 15第一趟扫到下标 5,这一格是 'd'(橙色高亮)。先看清是哪个字符,下一步把它的次数加一。
- 16把 'd' 的次数加一,它现在出现了 1 次。第一趟走完,cnt 就记下了每个字母各出现几次。
- 17第一趟扫到下标 6,这一格是 'e'(橙色高亮)。先看清是哪个字符,下一步把它的次数加一。
- 18把 'e' 的次数加一,它现在出现了 1 次。第一趟走完,cnt 就记下了每个字母各出现几次。
- 19第一趟结束。计数表已经数清:a 和 b 各出现 2 次,c、d、e 各出现 1 次。接下来第二趟按原顺序找第一个次数为 1 的字符。
- 20第二趟扫到下标 0 的 'a',查计数表它出现了 2 次,不止一次(标红),不是答案,继续往右看。
- 21第二趟扫到下标 1 的 'a',查计数表它出现了 2 次,不止一次(标红),不是答案,继续往右看。
- 22第二趟扫到下标 2 的 'b',查计数表它出现了 2 次,不止一次(标红),不是答案,继续往右看。
- 23第二趟扫到下标 3 的 'b',查计数表它出现了 2 次,不止一次(标红),不是答案,继续往右看。
- 24第二趟扫到下标 4 的 'c',去计数表一查,它的次数是 1——这就是第一个只出现一次的字符。停下来,答案就是下标 4。
- 25整道题做完:第一个只出现一次的字符是 'c',它的下标是 4,返回 4。
⚠️ 容易写错的地方
✗ 错:边扫边判断「之前没见过就当答案」
✓ 对:先完整数完一趟,再回头找
一个字符是不是唯一,要看它在整个串里出现几次;只看左边一半会误判
✗ 错:第二趟用计数表的键序去找
✓ 对:第二趟必须按字符串原下标顺序扫
题目要「第一个」,是按出现位置算的,不是按字母顺序
✗ 错:忘了没有唯一字符的情况
✓ 对:两趟都没命中时返回 -1
像 "aabb" 这种全是重复字符的串,必须返回 -1
完整代码(Python / C++ / Java)
Python
def firstUniqChar(s):
from collections import Counter
cnt = Counter(s) # 第一趟:数清每个字符出现几次
for i, ch in enumerate(s): # 第二趟:按原顺序找
if cnt[ch] == 1: # 第一个只出现一次的
return i
return -1 # 没有则返回 -1C++
int firstUniqChar(string s){
int cnt[26] = {0};
for (char ch : s) cnt[ch - 'a']++; // 第一趟计数
for (int i = 0; i < s.size(); i++) // 第二趟扫描
if (cnt[s[i] - 'a'] == 1) return i;
return -1;
}Java
public int firstUniqChar(String s) {
int[] cnt = new int[26];
for (char ch : s.toCharArray()) cnt[ch - 'a']++; // 第一趟
for (int i = 0; i < s.length(); i++) // 第二趟
if (cnt[s.charAt(i) - 'a'] == 1) return i;
return -1;
}复杂度
时间
O(n)
两趟都只把字符串扫一遍,n 是字符串长度
空间
O(1)
计数表最多 26 个小写字母,大小固定,与 n 无关
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 字符串中的第一个唯一字符 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么空间是 O(1) 而不是 O(n)?+
因为只有小写字母时,计数表最多 26 个槽,是个固定常数,跟字符串多长无关。若字符集更大(如完整 Unicode),可视作 O(字符集大小)。
能不能只扫一趟就解决?+
单纯一趟不行,因为扫到某字符时还不知道它后面会不会重复。可以用「哈希表记录每个字符最后出现的下标 + 一个候选集合」的技巧逼近,但本质仍需看完整个串,标准解法就是两趟最清晰。
如果要返回的是字符本身而不是下标呢?+
第二趟命中时返回 s[i] 即可;逻辑完全一样,只是返回值从下标换成字符。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 字符串中的第一个唯一字符 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。