这是一道面试题。找到给定字符串中的第一个字符,它只出现一次(我想解决方案应该是 Java)。
例如:
"babcbcd" -> 'a' // 'a' 和 'd' 都只出现一次,但 'a' 出现在 'd' 之前
简单的解决方案是
- 构建地图(例如
HashMap
):char -> 它在字符串中出现的次数; - 针对映射扫描字符串并测试字符串字符,直到字符的值为1。
是否有意义?最好的地图实现是什么?有没有更好、更有效的解决方案?
这是 Haskell 中的一个版本。
import Data.List (elemIndices)
firstSingle str = take 1 [a | a <- str, length (elemIndices a str) == 1]
*Main Data.List> firstSingle "babcbcd"
"a"
在 Java 中,您可以使用 aLinkedHashMap<Character,Integer>
来计算每个字符在字符串中出现的次数。由于LinkedHashMap
保留了插入顺序,因此您可以遍历其条目以找到恰好出现一次的第一个字符。
在合理的假设下,这将给出一个O(n)
解决方案。
如果我在面试,我会希望(虽然不一定期待)这样的事情:
def string = 'babcbcd'
def singles = string.toList().groupBy{ it }.findAll{ it.value.size() == 1 }.collect{ it.key }
println "First char that appears once: " + string.find{ singles.contains it }
关键是中线。它将字符串作为字符列表,按字符对列表进行分组,这样您就可以过滤掉没有恰好出现一次的任何内容,最后生成出现过一次的字符列表。然后我们只需在字符串中搜索该列表中的第一个字符。
它之所以是 Groovy,是因为在 Java 中不可能如此优雅。也许一旦 JDK 8 终于成功了……
更新:一个更简洁的版本,灵感来自@groovy 的 Haskell 解决方案。我现在几乎为我的第一个笨拙而尴尬:)
def firstUnique(seq) { seq.findAll{ seq.count(it) == 1 }.first() }
所有上述解决方案都需要 O(n) 内存。为了在 O(1) 内存中执行此操作,您可以为所有字符运行 for 循环(在 ASCII 中有 128 个)并计算出现次数和第一次出现,然后找到第一个非重复字符。时间复杂度 O(128|s|)。
int min=Integer.MAX_VALUE;
char ans=' '; //any initialization
for ( i=0; i<128;i++){
int count=0,first=-1;
for(j=0;j<s.length();j++){
if(s.charAt(j)==(char)(i)) count++;
if(count==1) first=j;
if(count>1) break;
}
if(count==1 && min>first){
first=min;ans=s.charAt(first);
}
}
if(min!=Integer.MAX_VALUE) System.out.println(ans);
else System.out.println("No such char found");
假设String
仅由 az 个字符组成,您可以对目前看到的字符使用队列并保持计数。
public static String findFirstLetterThatAppearsOnceInString(String str) {
int[] seenCount = new int[26];
int[] queue = new int[26];
int queueSize = 0;
for (char c : str.toCharArray()) { // Iterate over the input characters
int i = c-'a';
if (seenCount[i]<2) {
seenCount[i]++;
// If seen for the first time, store it in queue
if (seenCount[i]==1) queue[queueSize++]=i;
}
}
for (int qi=0;qi<queueSize;qi++) if (seenCount[queue[qi]]==1) return (char)(queue[qi]+'a')+"";
return null;
}
简单的非地图解决方案,假设没有 unicode:
public String firstLonelyChar(String input)
{
while(input.length() > 0)
{
int curLength = input.length();
String first = String.valueOf(input.charAt(0));
input = input.replaceAll(first, "");
if(input.length() == curLength - 1)
return first;
}
return null;
}
您可以在原始字符串的一次传递中完成:制作一个链接的哈希映射,存储您到目前为止找到的字符的计数。然后浏览地图条目(它将按插入顺序排列,因为它是一个链接地图)并在看到计数为 1 时停止。
您已经找到了一个很好的解决方案,但如果您愿意,我建议采用不同的方法:
final String abc = "abcdefg....z";
boolean scanned[] = new boolean[abc.lenght()];
//set all to false ...
for(int i = 0; i<yourString.lenght(); i++){
char c = yourString.charAt(i);
if(!scanned[abc.indexOf(c)]){
for(int j=i+1; j<yourString.lenght(); j++)
if(yourString.charAt(i) == c){ // founded another
scanned[abc.indexOf(c)] = true;
break;
}
if(!scanned[abc.indexOf(c)])
return c;
}
}