1. 题目描述
2. 解题思路
一开始看到这条题目我就产生一种想法,我看到一个数,我再往后看看它还有没有出现不就得了?但当我尝试判断“aabb”这种字符串时发现,判断第一个a是没有问题的,但判断第二个a时后面已经是没有a的存在,会误判,因此在这个算法中必须让他有“记忆”,需要使用哈希表储存前面曾经出现的字符以免误判。
实现第一种方法后发现性能不太优,既然我对每个字符都要看一眼后面,那我也可以统计所有字符后再进行判断,正如我在【LeetCode - Java】350. 两个数组的交集 II (简单)中所说的,对于这种需要“全局观”的题目来说,统计是一个很好的方法,我们可以统计每一个字符的出现次数,然后再遍历一次字符串,判断其出现次数是否为1,当然使用哈希表记录出现次数的没有问题的,但对于这种字符种类数不大且确定的(小写英文字母26个),使用数组的速度会快很多。
噢?可以用数组代替哈希表,那么第一种方法自然也可以,于是也尝试了一番发现比“全局观”还要更好一些。
3. 代码实现
3.1 逐位判断 哈希表记忆
public int firstUniqChar(String s) {
HashSet<Character> set = new HashSet<>();
for (int i = 0; i < s.length(); i++) {
int j;
if (!set.contains(s.charAt(i))) {
for (j = i + 1; j < s.length(); j++) {
if (s.charAt(i) == s.charAt(j)) {
set.add(s.charAt(i));
break;
}
}
if (j == s.length())
return i;
}
}
return -1;
}
3.2 统计次数 哈希表
public int firstUniqChar(String s) {
Hashtable<Character, Integer> table = new Hashtable<>();
for (int i = 0; i < s.length(); i++) {
if(table.putIfAbsent(s.charAt(i),1)!=null){
table.put(s.charAt(i),table.putIfAbsent(s.charAt(i),1)+1);
}
}
for (int i = 0; i < s.length(); i++) {
if(table.get(s.charAt(i))==1)
return i;
}
return -1;
}
3.3 统计次数 数组
public int firstUniqChar(String s) {
int[] count = new int[26];
for (int i = 0; i < s.length(); i++) {
count[s.charAt(i) - 'a'] = count[s.charAt(i) - 'a'] + 1;
}
for (int i = 0; i < s.length(); i++) {
if (count[s.charAt(i) - 'a'] == 1)
return i;
}
return -1;
}
3.4 逐位判断 数组
int[] count = new int[26];
for (int i = 0; i < s.length(); i++) {
int j;
if (count[s.charAt(i) - 'a'] == 0) {
for (j = i + 1; j < s.length(); j++) {
if (s.charAt(i) == s.charAt(j)) {
count[s.charAt(i) - 'a']++;
break;
}
}
if (j == s.length())
return i;
}
}
return -1;
3.5 对比
逐位判断因为每判断一位元素都需要向后看一眼,因此其时间复杂度是O(n²),而统计方法的时间复杂度是O(n),单纯从时间复杂度的对比而言,统计是比逐位判断更优的,但由于诸位判断中带有一种“早停”的性质,因此在实际表现上却是逐位判断比统计更优。对于空间复杂度而言,使用哈希表的空间复杂度都是O(n),使用数组的空间复杂度是O(1),不过同样由于哈希表所存储的元素一定是会小于等于26的,因此实际表现上哈希表的空间表现会更优。