题目链接:https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/
1. 题目介绍(50. 第一个只出现一次的字符)
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
【测试用例】:
示例1:
输入:s = “abaccdeff”
输出:‘b’
示例2:
输入:s = “”
输出:’ ’
【条件约束】:
限制:
- 0 <= s 的长度 <= 50000
【相关题目】:
注意:本题与主站 387. 字符串中的第一个唯一字符 题目相同.
其它题目:
【题目1】: 从一个字符串中删除在另一个字符串中出现过的所有字符 (OR63 删除公共字符)。定义一个函数,输入两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有字符。例如,输入 ”They are
students.” 和 ”aeiou” ,则删除之后的第一个字符串变成 ”Thy r stdnts.”
……
【Solution】:为了解决这个问题,我们可以创建一个用数组实现的简单哈希表
来存储第二个字符串。这样我们从头到尾扫描第一个字符串的每个字符时,用O(1)
时间就能判断出该字符是不是在第二个字符串中。如果第一个字符串的长度是 n, 那么总的时间复杂度是O(n)
.
【题目2】:删除字符串中所有重复出现的字符(316. 去除重复字母)。定义一个函数,删除字符串中所有重复出现的字符。例如:输入“google”,删除重复字符之后的结果是“gole”。
……
【Solution】:我们可以创建一个boolean数组
来实现简单的哈希表。数组中的元素的意义是其下标看作ASCII
码后对应的字母在字符串中是否已经出现。 我们先把数组中所有的元素都设为false
。以“google”
为例,当扫描到第一个g
时,g
的 ASCII 码是 103,那么我们把数组中下标位103 的元素设为true
。当扫描到第二个g
时,我们发现数组中下标位 103 的元素的值是true
,就知道g
在前面已经出现过。
……
【Supplementary】:316. 去除重复字母 题目中除了要求去除重复字母,还要求返回值是最小字典序
。因此,在这一题中,我们还加入了单调栈
来判断并存储 最小字典序的去重字符串。
【题目3】:变位词(剑指 Offer II 032. 有效的变位词)。该题有很多变形题,后面可以统一的练一练。在英语中,如果两个单词中出现的字母相同,且每个字母出现的次数也相同,那么这两个单词互为变位词(Anagram)。例如,
silent
与listen
、evil
与live
等互为变位词。请完成一个函数,判断输入的两个字符串是不是互为变位词。
……
【Solution】:我们可以创建一个用数组实现的简单哈希表
,用来统计字符串中每个字符出现的次数。
- 当扫描到第一个字符串中的每个字符时,为哈希表对应的项的值增加1;
- 接下来去扫描第二个字符串,当扫描到每个字符时,为哈希表对应的项的值减去1;
- 如果扫描完第二个字符串后,哈希表中所有的值都是0,那么这两个字符串就互为变位词。
【举一反三】:
如果需要判断 多个字符是不是在某个字符串里出现过 或者 统计多个字符在某个字符串中出现的次数,那么我们可以考虑
基于数组创建一个简单的哈希表
,这样可以用很小的空间消耗换来时间效率的提升。
2. 题解
2.1 枚举 – O(n2)
时间复杂度O(n2),空间复杂度O(n)
【解题思路】:
该题最直观的想法就是 从头开始扫描这个字符串中的每个字符。当访问到某字符时,拿这个字符和后面的每个字符相比较,如果字符串有n
个字符,则每个字符可能与后面的O(n)
个字符相比较,因此这种思路的时间复杂度是 O(n2)。
……
【实现策略】:
- 双层循环遍历字符串,将每个字符都与字符串比较一遍;
- 通过变量
count
来记录当前字符在字符串中出现的次数,找出第一个无重复的字符后返回。
class Solution {// Solution1:枚举public char firstUniqChar(String s) {// 遍历每一个字符,寻找无重复的字符for (int i = 0; i < s.length(); i++){// 记录当前字符出现的次数int count = 0;char curr = s.charAt(i);for (int j = 0; j < s.length(); j++) {if (curr == s.charAt(j)) count++;}// 遍历完一遍字符串,如果count值为1,则返回if(count == 1) return curr;}// 没有无重复字符,返回单空格return ' ';}
}
2.2 哈希表 – O(n)
时间复杂度O(n),空间复杂度O(n)
【解题思路】:
题目与字符出现的次数有关,那么我们就可以通过哈希表来统计每个字符在该字符串中出现的次数。哈希表的键为字符,值为该字符出现的次数。
……
【实现策略】:
- 定义哈希表
map
;- 第一次遍历字符串,存入哈希表,用来统计该字符串中字符分别出现的个数;
- 第二次遍历字符串,从哈希表中找出 值为1的字符 返回。
class Solution {// Solution2:哈希public char firstUniqChar(String s) {// 定义哈希表HashMap<Character,Integer> map = new HashMap<>(); // 遍历一遍字符串,存入哈希表for (int i = 0; i < s.length(); i++){if (map.containsKey(s.charAt(i))) map.put(s.charAt(i),map.get(s.charAt(i)) + 1);else map.put(s.charAt(i),1);}// 第二次遍历字符串,找出第一个value值为1的返回for (int i = 0; i < s.length(); i++){if (map.get(s.charAt(i)) == 1) return s.charAt(i);}// 没有无重复字符,返回单空格return ' ';}
}
有序哈希表:
【解题思路】:
在哈希表的基础上,有序哈希表中的键值对是 按照插入顺序排序 的。基于此,可通过遍历有序哈希表,实现搜索首个 “数量为 1 的字符”。
Java 使用
LinkedHashMap
实现有序哈希表。LinkedHashMap
底层借助哈希桶+双向链表,就是在hashmap的基础上通过双向链表维护元素节点间的顺序。如果想要查找效率快且维护插入顺序的话,用它就对啦,默认的就是按插入顺序排列。
class Solution {public char firstUniqChar(String s) {Map<Character, Boolean> dic = new LinkedHashMap<>();char[] sc = s.toCharArray();for(char c : sc)dic.put(c, !dic.containsKey(c));for(Map.Entry<Character, Boolean> d : dic.entrySet()){if(d.getValue()) return d.getKey();}return ' ';}
}
3. 参考资料
[1] 面试题50. 第一个只出现一次的字符(哈希表 / 有序哈希表,清晰图解)