目录
https://leetcode.com/problems/first-unique-character-in-a-string/
返回给定字符串第一个只出现一次的字符的下标,不存在则返回-1
一、问题描述
测试用例:
s = "leetcode"
return 0.
s = "loveleetcode",
return 2.
字符串只有小写字母。
二、代码实现
1、两遍遍历
class Solution {
public int firstUniqChar3(String s) {
int[] pos = new int[26]; //记录26个字母第一次出现的位置,如果没有出现或者已经重复则为-1
Arrays.fill(pos, -1);
Set<Character> occurence = new HashSet<>();
//O(n)
for(int i=0; i<s.length(); i++) {
char ch = s.charAt(i);
if(!occurence.contains(ch)) {
occurence.add(ch);
pos[ch - 'a'] = i;
} else {
pos[ch - 'a'] = -1;
}
}
//O(m) m是字符的范围,这里是26
int minIndex = Integer.MAX_VALUE;
for (int i = 0; i < 26; i++) {
if (pos[i] != -1) {
minIndex = Math.min(minIndex, pos[i]);
}
}
return minIndex == Integer.MAX_VALUE ? -1 : minIndex;
}
public int firstUniqChar2(String s) {
//1、得到每个字母的出现次数信息
LinkedHashMap<Character, Integer> map = new LinkedHashMap<>();
for (int i=0; i<s.length(); i++) {
char ch = s.charAt(i);
map.put(ch, map.getOrDefault(ch, 0) + 1);
}
//2、获取第一个只出现一次的字母的下标
for (Character key : map.keySet()) {
if (map.get(key).equals(1)) {
return s.indexOf(key);
}
}
return -1; //所有字符都重复,不存在只出现一次的字符
}
public int firstUniqChar1(String s) {
if (s == null) {
return -1;
}
//1、得到每个字母的出现次数信息
int[] freq = new int[26]; //题目告诉我们只有小写字母
for(int i=0; i<s.length(); i++) {
freq[s.charAt(i) - 'a']++;
}
//2、获取第一个只出现一次的字母的下标
for(int i=0; i<s.length(); i++) {
if(freq[s.charAt(i) - 'a'] == 1) {
return i;
}
}
return -1;
}
}
2、一遍遍历
当字符串长度非常长时,并且第一个只出现一次的字符在很靠后的位置才出现时,两遍遍历的时间效果其实不是很好,这里用一遍遍历来改进一下。
class Solution {
//使用数组实现
public int firstUniqChar5(String s) {
if (s==null || s.length()==0) {
return -1;
}
if (s.length() == 1) {
return 0;
}
int len = s.length();
char[] arr = s.toCharArray();
int[] count = new int[256]; //计算字母出现次数,实际使用字符是Unicode而不是ASCII,所以256是不够的,最好还是使用HashMap
int slow = 0, fast = 1; //快指针用于遍历字符串,慢指针指向可能只出现一次的字母
count[arr[slow]]++;
while (fast < len) {
count[arr[fast]]++; //添加已经访问的标志
//如果慢指针不是独立的字母,则将其移动到下一个可能只出现一次的字母
while (slow<len && count[arr[slow]]>1) {
slow++;
}
//没有只出现一次的字母
if (slow >= len) {
return -1;
}
//还没有被快指针访问到:这一段代码有些时候可以让快指针减少剩余待访问的元素
// if (count[arr[slow]] == 0) {
// fast = slow; //重新定位快指针
// count[arr[slow]]++;
// }
fast++; //快指针指向下一个待访问的字母
}
return slow;
}
//使用集合实现
//一次遍历,当字符串很长并且字符个数很多(不止26个)的时候可以执行得很快,这里慢了些
//此外,这种方式在字符以数据流的方式到来的时候非常好
public int firstUniqChar4(String s) {
Map<Character, Integer> map = new LinkedHashMap<>(); //存放可能只出现一次的字母
Set<Character> set = new HashSet<>(); //存放已经遍历过的所有字母
for (int i = 0; i < s.length(); i++) {
if (set.contains(s.charAt(i))) { //如果已经访问过,则从map中删除它
//if (map.get(s.charAt(i)) != null) {
map.remove(s.charAt(i));
//}
} else {
map.put(s.charAt(i), i);
set.add(s.charAt(i)); //添加访问标记
}
}
//map.entrySet()返回一个Map.Entry<K, V>对象的集合————Set<Map.Entry<K, V>>
//遍历一个集合需要Iterator,得到下一个Map.Entry对象则需要迭代器的next()方法
//得到Map.Entry对象之后,则可以调用其getKey()方法、getValue()获得键、值信息
return map.size() == 0 ? -1 : map.entrySet().iterator().next().getValue();
}
}