如何在字符串中找到第一个不重复的字符
编写一个Java程序来查找一个字符串中第一个非重复的字符,这是在编程测试中很常见的一个问题,因为字符串处理在程序员面试中是一个普遍的话题。面试前最好是准备好一些熟知的编程问题,例如使用递归反转字符串,或者检查一个字符串是否是回文(即正反读顺序一致)。查找第一个非重复字符的问题也是在同一个范畴。在给出解决方案之前,我们先来弄懂这个问题。我们需要编写一个函数,这个函数接受一个字符串作为参数,并返回第一个不重复的字符。例如字符串“hello”,除了“l”之外所有字符都是不重复的,但是“h”是第一个不重复的字符。同样,字符串“swiss”中“w”是第一个不重复的字符。一种解决该问题的方法就是创建一张表来记录每个字符的出现次数,然后选出第一个不重复的元素。关键在于字符的次序,你的代码必须返回第一个非重复的字符。另外,在这篇文章中,我们可以看到3个解决该问题的示例。我们的第一个方案是使用LinkedHashMap来记录字符个数,因为LinkedHashMap维持的元素顺序与插入顺序一致,而我们正是按照字符串中字符出现的顺序来将字符插入Map中的。当我们扫描字符串时,只需要迭代LinkedHashMap并找出值为1的元素。是的,这种方案只需要一个LinkedHashMap以及两个循环。我们的第二种解决方案是时间和空间的折衷,在一次遍历中找出不重复的字符。这次,我们使用一个Set和一个List去分开保存重复和不重复的字符。当我们完成一次时间复杂度为O(n)的字符串扫描后,我们可以通过访问List来获得这个神奇的不重复字符,该时间为复杂度为O(1),因为List是有序的,我们可以通过get(0)获得第一个元素。我们的第三种解决方案也是类似的,不过这次我们使用HashMap而不是LinkedHashMap,我们会在第一扫描计算各个字符的出现次数之后,再次扫描字符串去找到第一个不重复的字符。接下来我们将为这个问题编写示例代码和单元测试程序。你也可以去我的字符串面试问题列表中看看更多类似的Java编程题。
这里有在给定字符串中找到第一个非重复字符的完整示例代码,该程序给出了三个找到第一个非重复字符的方法,每个方法都有自己的解决问题的算法。
第一个算法实现在getFirstNonRepeatedChar(String str)方法中。它首先获得字符串的字符数组,然后遍历数组并建立一个哈希表,哈希表的键为字符,值为该字符出现的次数。下一步它会遍历LinkedHashMap去找到第一个值为1的元素,那便是第一个非重复的字符,因为LinkedHashMap维护的元素顺序与插入顺序一致,而我们遍历字符数组是从头遍历到尾。这种算法的缺点在于它需要两个循环,第一个循环的次数与字符串的字符个数成正比,而第二个循环的次数与字符串中重复的字符个数成正比。最差的情况是非重复的字符出现在字符串的最尾部,那么这个算法需要2*N的时间去解决这个问题。
第二个算法实现在firstNonRepeatingChar(String word)方法中。这种解决方案可以在一次字符串扫描中找到第一个不重复的字符,它应用了典型的空间时间权衡技术。它使用了两个存储空间来减少一次循环,是标准的空间-时间折衷。由于我们将重复和不重复的字符分开存放,在循环结束后,存放不重复字符的列表中的第一个元素就是我们所要找的第一个非重复字符。这种解决方案稍微优于上一种。如果在字符串中没有不重复的字符,你可以选择返回null或者空字符串。
第三个算法实现在firstNonRepeatedCharacter(String word)方法中,它与第一种方案非常类似,唯一不同在于它使用了HashMap。由于HashMap并不保证一种特定的顺序,我们必须依赖源字符串去找到第一个不重复的字符。第三种解决方案的算法如下:
- 扫描字符串并将每个字符的出现次数保存在HashMap中
- 遍历字符串并从Map中获取每个字符的个数
由于我们从左往右扫描字符,当找到某个字符的计数为1时,我们就可以跳出循环,它就是第一个非重复的字符。在这里,字符次序是靠再次遍历源字符串来实现。
1//采用的第二种方法,将重复的元素放入到set集合,不重复的元素放入List集合。
//感觉这两部分都可以放入到list集合呀?试试就知道了。不能使用两个List集合,因为当你将重复元素往set集合里存时,list中不再包含此重复元素,当下次再遇到此重复元素时,又会存入list集合,并存入到set集合,假使此时set集合变为list集合的话,
//在其中中会包含两个相同元素。可以解决,将set提前判决就行。
import java.util.*; 2 3 public class Programming { 4 public static char firstNonRepeatingChar(String word) { 5 Set<Character> repeating = new HashSet<Character>(); 6 List<Character> nonRepeating = new ArrayList<Character>(); 7 for (int i = 0; i < word.length(); i++) { 8 char letter = word.charAt(i);//依次获得字符串中的每个字符 9 //System.out.println(letter); 10 //System.out.println(repeating); 11 if (repeating.contains(letter)) { 12 continue; 13 } 14 15 if (nonRepeating.contains(letter)) { 16 nonRepeating.remove((Character) letter);//这里强制转换和不强制转换区别这么大么?,如果这里输入的是char的话,默认转变为Int了 17 repeating.add(letter); 18 } else { 19 nonRepeating.add(letter); 20 } 21 System.out.println(nonRepeating); 22 } 23 return nonRepeating.get(0); 24 } 25 26 public static void main(String[] args) 27 { 28 Programming pro=new Programming (); 29 String str="hello"; 30 char c=pro.firstNonRepeatingChar(str); 31 System.out.println("输入的字符串为: "+str); 32 System.out.println("字符串中第一个非重复的字符为: "+c); 33 } 34 }
关于上述16行的强制转换可查看下面链接,这里重载是具有优先级的,假设不强制的话char类型默认转换为int型,并优先调用remove(int index)这个方法,而不是
remove(Object o)此方法。
转:http://shmilyaw-hotmail-com.iteye.com/blog/1447631