题目
给定一个字符串s
,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1
。
示例 1:
输入: s = “leetcode”
输出: 0
示例 2:
输入: s = “loveleetcode”
输出: 2
示例 3:
输入: s = “aabb”
输出: -1
方法一:使用哈希表存储频数
思路与算法
我们可以对字符串进行两次遍历
在第一次遍历时,通过hash映射统计每个字符出现的频数,如果只出现一次,设置他的值为1
,如果出现多次就设置值为0
或其他。
代码:
public int firstUniqChar(String s) {
int len = s.length();
Map<Character,Integer> map=new HashMap<>();
for (int i = 0; i < len; i++) {
map.put(s.charAt(i),map.getOrDefault(s.charAt(i),0)+1);
}
for (int i = 0; i < len; i++) {
if(map.get(s.charAt(i))==1){
return i;
}
}
return -1;
}
其中的map.getOrDefault()
是map自带的方法
其作用是:
当Map集合中有这个key时,就用这个key值,没有时就给他设置默认的值。
例如:
HashMap<String, String> map = new HashMap<>();
map.put("name", "cookie");
map.put("age", "18");
map.put("sex", "女");
String name = map.getOrDefault("name", "random");
System.out.println(name);// cookie,map中存在name,获得name对应的value
int score = map.getOrDefault("score", 80);
System.out.println(score);// 80,map中不存在score,使用默认值80
方法二:使用哈希表存储索引
感觉hash是真的方便,同样也是两次遍历。这次我们是直接把字符串中的字符作为存储的key,索引下标作为存储的value。如果遍历时key已经存在,设置其key的value值为-1
。如果key值不存在就设置key为当前的索引i
。
第二次遍历map找出出了-1
之外最小的i
就可以了。
最后就是i
遍历到最后是n
的话,同样也是返回-1。
class Solution {
public int firstUniqChar(String s) {
Map<Character, Integer> position = new HashMap<Character, Integer>();
int n = s.length();
for (int i = 0; i < n; ++i) {
char ch = s.charAt(i);
if (position.containsKey(ch)) {
position.put(ch, -1);
} else {
position.put(ch, i);
}
}
int first = n;
for (Map.Entry<Character, Integer> entry : position.entrySet()) {
int pos = entry.getValue();
if (pos != -1 && pos < first) {
first = pos;
}
}
if (first == n) {
first = -1;
}
return first;
}
}
方法三:队列
思路与算法
我们也可以借助队列找到第一个不重复的字符。队列具有「先进先出」的性质,因此很适合用来找出第一个满足某个条件的元素。
具体地,我们使用与方法二相同的哈希映射,并且使用一个额外的队列,按照顺序存储每一个字符以及它们第一次出现的位置。当我们对字符串进行遍历时,设当前遍历到的字符为 cc,如果 cc 不在哈希映射中,我们就将 cc 与它的索引作为一个二元组放入队尾,否则我们就需要检查队列中的元素是否都满足「只出现一次」的要求,即我们不断地根据哈希映射中存储的值(是否为 -1−1)选择弹出队首的元素,直到队首元素「真的」只出现了一次或者队列为空。
在遍历完成后,如果队列为空,说明没有不重复的字符,返回 -1−1,否则队首的元素即为第一个不重复的字符以及其索引的二元组。
在维护队列时,我们使用了「延迟删除」这一技巧。也就是说,即使队列中有一些字符出现了超过一次,但它只要不位于队首,那么就不会对答案造成影响,我们也就可以不用去删除它。只有当它前面的所有字符被移出队列,它成为队首时,我们才需要将它移除。
代码:
class Solution {
public int firstUniqChar(String s) {
Map<Character, Integer> position = new HashMap<Character, Integer>();
Queue<Pair> queue = new LinkedList<Pair>();
int n = s.length();
for (int i = 0; i < n; ++i) {
char ch = s.charAt(i);
if (!position.containsKey(ch)) {
position.put(ch, i);
queue.offer(new Pair(ch, i));
} else {
position.put(ch, -1);
while (!queue.isEmpty() && position.get(queue.peek().ch) == -1) {
queue.poll();
}
}
}
return queue.isEmpty() ? -1 : queue.poll().pos;
}
class Pair {
char ch;
int pos;
Pair(char ch, int pos) {
this.ch = ch;
this.pos = pos;
}
}
}
最后再介绍一道解法
public int firstUniqChar(String s) {
int[] arr = new int[26];
int n = s.length();
for (int i = 0; i < n; i++) {
arr[s.charAt(i)-'a']++ ;
}
for (int i = 0; i < n; i++) {
if (arr[s.charAt(i)-'a'] == 1) {
return i;
}
}
return -1;
}
是记录26个字母每个字母的数量的,两次循环,第一次找出每个字母的数量,第二次找出为值1的下标返回。
哈哈 算法是不是很有意思。。