题目描述
在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符的位置。若为空串,返回-1。位置索引从0开始。
分析
直观思路:从头到尾扫描字符串,访问到某个字符是将其和后面的每个字符比较。如果没有发现重复字符,则只出现一次,即找到该字符。但是如果字符串有n个字符,则每个字符需要与后面的 O(n) 个字符比较,总的时间复杂度为 O(n2) 。
推荐思路:只进行一趟扫描,时间复杂度为 O(n) ,利用一个map容器存储字符及其出现的次数,另外一个map容器存储字符及其第一次出现的索引。扫描完成后,通过第一个map可以在 O(1) 时间内找到只出现1次的字符(利用LinkedHashMap,保持输出和输入顺序一致),接着可以找到该字符第一次出现的索引。总的时间复杂度为 O(n) 。
牛客AC:
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
public class Solution {
public int FirstNotRepeatingChar(String str) {
if(str == null || str.length() <= 0)
return -1;
// LinkedHashMap 保证输出顺序与输入顺序一致
Map<Character, Integer> mapCount = new LinkedHashMap<Character, Integer>(); // 字符和对应的而次数
Map<Character, Integer> mapIndex = new LinkedHashMap<Character, Integer>(); // 字符和首次出现的索引
for(int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if(mapCount.containsKey(c)) {
int count = mapCount.get(c) + 1;
// mapCount.replace(c, count); // replace java1.8才开始支持
mapCount.remove(c);
mapCount.put(c, count);
} else {
mapCount.put(c, 1);
mapIndex.put(c, i);
}
}
char resChar = '0';
int resIndex = -1;
for(Entry<Character, Integer> entry : mapCount.entrySet()) {
// 只出现一次的字符和首次出现的索引
if(entry.getValue() == 1) {
resChar = entry.getKey();
resIndex = mapIndex.get(resChar);
//System.out.println(resIndex);
break;
}
}
return resIndex; // 如需返回首次出现的索引,修改即可
}
}
相关问题扩展
1、定义一个函数,输入两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有字符。例如从第一个字符串“We are students”中删除在第二个字符串“aeiou”中出现过的字符得到“W r Stdnts”。
思路:使用HashMap存储第二个字符串,对第一个字符串进行一次扫描,每扫描到一个字符就判断map.containsKey(),如果出现,则删除该字符。
public String removeAppeared(String iniStr, String appearStr) {
if(iniStr == null || iniStr.length() <= 0)
return null;
if(appearStr == null || appearStr.length() <= 0)
return iniStr;
// map存储第二个字符串
Map<Character, String> map = new HashMap<Character, String>();
for(int i = 0; i < appearStr.length(); i++) {
char c = appearStr.charAt(i);
map.put(c, null);
}
// 判断是否出现过
StringBuffer sb = new StringBuffer();
for(int i = 0; i < iniStr.length(); i++) {
char c = iniStr.charAt(i);
if(map.containsKey(c))
continue;
sb.append(c);
}
return sb.toString();
}
2、定义一个函数,删除字符串中所有重复出现的字符。例如输入“google”,删除重复字符之后的结果是“gole“。
思路:和上题类似,对字符串进行一趟扫描,每扫描到一个字符就判断map.containsKey(),如果出现,则删除该字符。
public String removeRepeat(String iniStr) {
if(iniStr == null || iniStr.length() <= 0)
return null;
Map<Character, Boolean> map = new HashMap<Character, Boolean>();
StringBuffer sb = new StringBuffer();
for(int i = 0; i < iniStr.length(); i++) {
char c = iniStr.charAt(i);
if(map.containsKey(c)) { // 如果map包含key,则该字符一定出现过
continue;
} else {
map.put(c, true);
sb.append(c);
}
}
return sb.toString();
}
3、在英语中,如果两个单词中出现的字母相同,并且每个字母出现的次数也相同,那么这两个单词互为变位词(Anagram)。例如silent与listen、evil与live等。请完成一个函数,判断输入的两个字符串是不是互为变位词。
思路:使用HashMap存储字符串中每个字符出现的次数。对第一个字符串扫描一次,每扫描一个字符,相应的字符的次数增加1;对第二个字符串扫描一次,每扫描一个字符,相应的字符的次数减去1。如果扫描完第二个字符后,HashMap中所有的值都是0,那么这两个字符串就是互为变位词。
public boolean isAnagram(String str1, String str2) {
if(str1 == null || str1.length() <= 0 ||
str2 == null || str2.length() <=0 || str1.length() != str2.length())
return false;
boolean isAnagram = false; // 返回值
// 遍历第一个字符串,存储字符及其次数
Map<Character, Integer> map = new HashMap<Character, Integer>();
for(int i = 0; i < str1.length(); i++) {
char c = str1.charAt(i);
if(map.containsKey(c)) {
int count = map.get(c) + 1; // 每次加1
map.remove(c);
map.put(c, count);
} else {
map.put(c, 1);
}
}
// 比那里第二个字符串,更新次数
for(int i = 0; i < str2.length(); i++) {
char c = str2.charAt(i);
if(map.containsKey(c)) {
int count = map.get(c) - 1; // 每次减1
map.remove(c);
map.put(c, count);
} else {
return isAnagram;
}
}
// 判断所有的字符的次数是否为0
for(Integer value : map.values()) {
if (value != 0)
return isAnagram;
}
isAnagram = true;
return isAnagram;
}
参考
1. 何海涛,剑指offer名企面试官精讲典型编程题(纪念版),电子工业出版社