【剑指 Offer 50. 第一个只出现一次的字符 】
】)
题目描述:
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/
- 在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例:
- 示例1:
输入:s = "abaccdeff"
输出:'b'
- 示例2:
输入:s = ""
输出:' '
- 提示:
0 <= s 的长度 <= 50000
解析思路:
方法一:哈希表+统计字符出现的次数
可以对字符串进行两次遍历,在第一次遍历时,我们使用哈希映射统计出字符串中每个字符出现的次数。在第二次遍历时,我们只要遍历到了一个只出现一次的字符,那么就返回该字符,否则在遍历结束后返回空格。
- 示意图:
代码(python3)
- Python 代码中的
not c in dic
整体为一个布尔值;c in dic
为判断字典中是否含有键c
class Solution:
def firstUniqChar(self, s: str) -> str:
dic = {} # 初始化容器:字典 (Python)、map(C++),记为 dic ;
''' 遍历+统计字符个数:
①、若 dic 中 不包含 键(key) c :则向 dic 中添加键值对 (c, True) ,代表字符 c 的数量为 1 ;
②、若 dic 中 包含 键(key) c :则修改键 c 的键值对为 (c, False) ,代表字符 c 的数量 > 1 。'''
for c in s:
dic[c] = not c in dic
# 遍历字符串 s 中的每个字符 c ;若 dic中键 c 对应的值为 True :,则返回 c
for c in s:
if dic[c]: return c
# 返回 ' ' ,代表字符串无数量为 1 的字符。
return ' '
代码(cpp)
解析思路:
class Solution {
public:
char firstUniqChar(string s) {
unordered_map<char, bool> dic;
for(char c : s)
dic[c] = dic.find(c) == dic.end();
for(char c : s)
if(dic[c]) return c;
return ' ';
}
};
复杂度分析:
- 时间复杂度 O(N) : N 为字符串 s 的长度;需遍历 s 两轮,使用 O(N) ;HashMap 查找操作的复杂度为 O(1) ;
- 空间复杂度 O(1) : 由于题目指出 s 只包含小写字母,因此最多有 26 个不同字符,HashMap 存储需占用 O(26) = O(1)的额外空间。
方法二:有序哈希表+统计字符出现的次数
在哈希表的基础上,有序哈希表中的键值对是 按照插入顺序排序 的。基于此,可通过遍历有序哈希表,实现搜索首个 “数量为 1 的字符”。
**哈希表是 去重 的,即哈希表中键值对数量 ≤ 字符串 s 的长度。**因此,相比于方法一,方法二减少了第二轮遍历的循环次数。当字符串很长(重复字符很多)时,方法二则效率更高。
代码(python)
class Solution:
def firstUniqChar(self, s: str) -> str:
dic = {}
for c in s:
dic[c] = not c in dic
for k, v in dic.items():
if v: return k
return ' '
代码(cpp)
class Solution {
public:
char firstUniqChar(string s) {
vector<char> keys;
unordered_map<char, bool> dic;
for(char c : s) {
if(dic.find(c) == dic.end())
keys.push_back(c);
dic[c] = dic.find(c) == dic.end();
}
for(char c : keys) {
if(dic[c]) return c;
}
return ' ';
}
};
方法3: 队列法
- (力扣官方参考思路)
- 来源:力扣(LeetCode)
我们也可以借助队列找到第一个不重复的字符。队列具有「先进先出」的性质,因此很适合用来找出第一个满足某个条件的元素。
具体地,我们使用与方法二相同的哈希映射,并且使用一个额外的队列,按照顺序存储每一个字符以及它们第一次出现的位置。当我们对字符串进行遍历时,设当前遍历到的字符为 c,如果 c不在哈希映射中,我们就将 c 与它的索引作为一个二元组放入队尾,否则我们就需要检查队列中的元素是否都满足「只出现一次」的要求,即我们不断地根据哈希映射中存储的值(是否为 -1)选择弹出队首的元素,直到队首元素「真的」只出现了一次或者队列为空。
在遍历完成后,如果队列为空,说明没有不重复的字符,返回空格,否则队首的元素即为第一个不重复的字符以及其索引的二元组。
代码(python)
class Solution:
def firstUniqChar(self, s: str) -> str:
position = dict()
q = collections.deque()
n = len(s)
for i, ch in enumerate(s):
if ch not in position:
position[ch] = i
q.append((s[i], i))
else:
position[ch] = -1
while q and position[q[0][0]] == -1:
q.popleft()
return ' ' if not q else q[0][0]
代码(cpp)
class Solution {
public:
char firstUniqChar(string s) {
unordered_map<char, int> position;
queue<pair<char, int>> q;
int n = s.size();
for (int i = 0; i < n; ++i) {
if (!position.count(s[i])) {
position[s[i]] = i;
q.emplace(s[i], i);
}
else {
position[s[i]] = -1;
while (!q.empty() && position[q.front().first] == -1) {
q.pop();
}
}
}
return q.empty() ? ' ' : q.front().first;
}
};