剑指 Offer 50. 第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例:
s = “abaccdeff”
返回 “b”
s = “”
返回 " "
限制:
0 <= s 的长度 <= 50000
解题思路
直接遍历一遍字符串,用Map保存每个字符出现了多少次,再次遍历字符串,找到第一个只出现一次的字符即可。
Java代码
class Solution {
public char firstUniqChar(String s) {
//从头至尾遍历每个字符,用一个Map存好,key为字符,value为出现的次数
if(s == null || s.length() == 0) return ' ';
Map<Character,Integer> map = new HashMap<>();
for(int i = 0;i < s.length();i++){
map.merge(s.charAt(i),1,Integer::sum);//merge方法介绍在后面
}
for(int i = 0;i < s.length();i++){
if(map.get(s.charAt(i)) == 1){
return s.charAt(i);
}
}
return ' ';
}
}

merge方法介绍
处理映射时的一个难点就是更新映射项。正常情况下,可以很容易的得到一个键关联的原值,完成更新,再放回更新后的值。不过必须考虑一个特殊情况,即键第一次出现。比如类似上面的例子,我们需要使用一个映射统计一个单词在文件中出现的频度。看到一个单词(word)时,我们将计数器增1,学过C++的朋友肯定觉得太简单了,直接counts[word]++即可,因为counts中没有为word的键时,会自动添加该键。但Java中就没有那么方便了,Java中的做法如下所示:
counts.put(word,counts.get(word)+1);
这是可以的,不过有一种情况除外:就是word是第一次出现时。在该情况下,get会返回null,因此会出现一个NullPointerException异常。
避免该情况一个简单的做法是使用getOrDefault方法:
counts.put(word,counts.getOrDefault(word,0) + 1);
当然也可以使用putIfAbsent方法。
//如果传入的key已经有对应的value存在,返回存在的value,若不存在,则添加key和value,返回null
counts.putIfAbsent(word,0);
counts.put(word,counts.get(word) + 1);
但是还有更好的做法,merge方法可以简化这个常见的操作。
counts.merge(word,1,Integer::sum);
如果键原先不存在,将把word与1关联,否则使用Integer.sum函数组合原值和1(也就是将原值与1求和)。
merge方法在下算法题和以及工作中还是很常用的,应该吃透。
下面贴一下Map中merge方法的源码:
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
这里的源码还是很简单的,基本不用解释了。
扩展题 LeetCode 387. 字符串中的第一个唯一字符
给定一个字符串 s ,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1 。
示例 1:
输入: s = "leetcode"
输出: 0
示例 2:
输入: s = "loveleetcode"
输出: 2
示例 3:
输入: s = "aabb"
输出: -1
提示:
- 1 <= s.length <= 10^5
- s 只包含小写字母
解题思路
与上题一模一样,唯一区别就是上题是返回字符,这题是返回对应的索引。
Go代码
func firstUniqChar(s string) int {
// 第一轮遍历,用一个map记录每个字符出现的次数
// 第二轮遍历,当前字符只出现过一次,则返回该字符对应的索引即可
m := make(map[byte]int)
for i := 0;i < len(s);i++ {
m[s[i]]++
}
for i := 0;i < len(s);i++ {
if m[s[i]] == 1 {
return i
}
}
return -1
}

本文提供了一种高效的方法来解决剑指Offer50题目中的“第一个只出现一次的字符”。通过两次遍历字符串并利用HashMap记录字符出现次数,快速定位到目标字符。
2031






