剑指 offer 50 第一个只出现一次的字符
哈希表存储频数
01
class Solution {
public char firstUniqChar(String s) {
if(s.isEmpty()){
return ' ';
}
int len = s.length();
Map<Character,Integer> map = new HashMap<>();
for (int i = len-1; i >= 0; i--) {
char c = s.charAt(i);
if (map.containsKey(c)){
map.replace(c,map.get(c)+1);
}else{
map.put(c,1);
}
}
for (int i=0;i<len;i++){
if (map.get(s.charAt(i))==1){
return s.charAt(i);
}
}
return ' ';
}
}
02
map.getOrDefault(c,0)的使用!!!
class Solution {
public char firstUniqChar(String s) {
if(s.isEmpty()){
return ' ';
}
int len = s.length();
Map<Character,Integer> map = new HashMap<>();
for (int i = len-1; i >= 0; i--) {
char c = s.charAt(i);
map.put(c,map.getOrDefault(c,0)+1);
}
for (int i=0;i<len;i++){
if (map.get(s.charAt(i))==1){
return s.charAt(i);
}
}
return ' ';
}
}
时间复杂度
O
(
n
)
O(n)
O(n),
n
n
n是字符串的长度,需要扫描两次字符串。
空间复杂度
O
(
∣
Σ
∣
)
O(|\Sigma |)
O(∣Σ∣),
Σ
\Sigma
Σ是字符集,在本题中
s
s
s只包含小写字母,因此
Σ
<
=
26
\Sigma<=26
Σ<=26,我们需要
O
(
∣
Σ
∣
)
O(|\Sigma|)
O(∣Σ∣)的空间存储哈希映射。
使用哈希表存储索引
class Solution {
public char firstUniqChar(String s) {
if(s.isEmpty()){
return ' ';
}
int len = s.length();
Map<Character,Integer> map = new HashMap<>();
for (int i = len-1; i >= 0; i--) {
char c = s.charAt(i);
if (map.containsKey(c)){
map.replace(c,-1);
}else{
map.put(c,i);
}
}
int first = len;
for (Map.Entry<Character,Integer> entry:map.entrySet()){
int pos = entry.getValue();
if (pos!=-1 && pos<first){
first = pos;
}
}
if (first==len){
return ' ';
}else {
return s.charAt(first);
}
}
}
时间复杂度
O
(
n
)
O(n)
O(n)。
空间复杂度
O
(
∣
Σ
∣
)
O(|\Sigma|)
O(∣Σ∣)。
队列
也可以借助队列找到第一个不重复的字符。队列具有先进先出的性质,因此很适合用来找出第一个满足某个条件的元素。
具体地,使用与第二种方法相同的哈希映射,并且使用一个额外的队列,按照顺序存储每一个字符以及他们第一次出现的位置。当对字符串进行遍历时,设当前遍历到的字符为
c
c
c,如果
c
c
c不在哈希映射中,就将
c
c
c与它的索引作为一个二元组放入队尾,否则就需要检查队列中的元素是否都满足“只出现一次”的要求,即我们不断地根据哈希映射中存储的值是否为
−
1
-1
−1选择弹出队首元素,直到队尾元素真的只出现一次或者队列为空。
在遍历完成后,如果队列为空,说明没有不重复的字符,返回空格,否则队首的元素就是第一个不重复的字符。
在维护队列时,使用了延迟删除的技巧。也就是说,队列中有些字符出现了超过一次,但它只要不位于队首(不是第一个,后面想怎么重复怎么重复,找到第一个不重复的就行),那么就不会对答案造成影响,我们也就可以不用去删除它。只有当它前面的所有字符被移出队列,它成为队首时,我们才需要将它移除。
class Solution {
public char firstUniqChar(String s) {
if(s.isEmpty()){
return ' ';
}
int len = s.length();
Map<Character,Integer> map = new HashMap<>();
Queue<Pair> queue = new LinkedList<>();
for (int i = 0; i <len; i++) {
char c = s.charAt(i);
if (map.containsKey(c)){
map.replace(c,-1);
while ((!queue.isEmpty())&&map.get(queue.peek().ch)==-1){
queue.poll();
}
}else{
map.put(c,i);
queue.add(new Pair(c,i));
}
}
if (queue.isEmpty()){
return ' ';
}
return queue.peek().ch;
}
}
class Pair{
char ch;
int pos;
public Pair(char ch, int pos) {
this.ch = ch;
this.pos = pos;
}
}
时间复杂度
O
(
n
)
O(n)
O(n),遍历字符串的时间复杂度是
O
(
n
)
O(n)
O(n),在遍历的过程还维护了一个队列,由于每一个字符最多只会被放入和弹出队列最多各一次,因此维护队列的总时间复杂度是
O
(
∣
Σ
∣
)
O(|\Sigma|)
O(∣Σ∣),由于
s
s
s包含的字符种类数一定小于
s
s
s的长度,因此
O
(
∣
Σ
∣
)
O(|\Sigma|)
O(∣Σ∣)在渐进意义下小于
O
(
n
)
O(n)
O(n),可以忽略。
空间复杂度
O
(
∣
Σ
∣
)
O(|\Sigma|)
O(∣Σ∣)。