题目:在一个字符串中找到第一个没有重复元素的字符并返回。

例:输入:"yellow"   

  返回:“y”

  输入:"tooth"

  返回:“h”

  输入:“coco”

  返回:“”

这个题目我在亚马逊电话面试中遇见过,后来再一家日本公司面试中也遇见过。尽管这个问题并没有涉及到比较高级的编程思想,比如动态编程dynamic Programming或者Divide and Conquer,但是如果对数据结构或者算法并不很熟练的人来说,还是会构成一定的困难。

好了,那么现在就来分析一下这个问题。

首先,最直观的解法就是利用循环挨个儿从第一个元素起往后面找有没有重复,如果遍历完成后还没有,那么这个元素就是第一个没有重复的元素,直接在循环体中返回值就可以了。那么具体解法就需要两个循环控制,第一个用来定位要查看的元素,第二个循环用来在字符串中找有没有跟他重复的元素。这个算法的时间复杂度是O(n2), 因为假设这个字符串中元素都有重复,那么第一个循环需要遍历n次,第二个循环也需要遍历n次。空间复杂度是O(c),为常数,因为我们并没有添加新的数据结构。Java代码如下:

public static String findCharInPlace(String s){
		int len = s.length();
		if(len <= 0) return null;
		boolean repeated = false;
		for(int i=0;i<s.length();i++){
			repeated = false;
			int j=0;
			for(;j<s.length();j++){
				if(j!=i&&s.charAt(j)==s.charAt(i)){
					repeated = true;
					break;
				}
			}
			if(!repeated)
				return s.charAt(i)+"";
		}
		
		return  "";
	}

  那么现在我们来看看有没有第二种解法。我们注意到,在Java中的Map类。我们可以用HashMap映射这个字符串的元素作为key,让每一个元素对应一个重复出现次数的标记,这个标记我们用一个Integer。HashMap<Character, Integer>.我们从头遍历这个字符串,如果后面重复出现了之前出现过的元素,我们就相应这个元素对应的重复出现标记加一。最后在遍历这个Map,找到第一个重复出现标记为一的元素并返回。可这里问题不仅要找到没有重复元素的元素,还要是返回第一个不重复的。但是HashMap的一个问题就是它是无序的。EntrySet并不会按照插入次序来排列,所以怎么办呢。我们知道Java中LinkedList是有序的。所以这里我们就可以用到LinkedHashMap来实现这个功能。 LinkedHashMap结合了LinkedList和HashMap的优点,得到的EntrySet是由一个内部的LinkedList来维护的。所以我们就可以用这个神奇的类来实现这个解法。代码如下:

public static String findChar(String s) {
		String ans = null;
		Map<Character, Integer> sMap = new LinkedHashMap<Character, Integer>();
		for(char c: s.toCharArray()){
			if(!sMap.containsKey(c)){
				sMap.put(c, 1);
			}else{
				sMap.put(c, sMap.get(c)+1);
			}
		}
		for(Entry<Character, Integer> en: sMap.entrySet()){
			if(en.getValue()==1) return en.getKey().toString();
		}
		return ans;
	}

  现在我们来分析一下这个算法的复杂度。LinkedHashMap中add,contail和remove并没有因为有了LinkedList这个特性而增加复杂度,依然是常数级别的复杂度。所以第一个遍历标记的时间复杂度是O(n),第二遍遍历查找的时间复杂度也是O(n),所以整个算法的时间复杂度是O(n).因为我们新添加了LinkedHashMap来对每个字符进行标记,所以空间复杂度是O(n).但从时间上来讲,还是相当快的。

  那么我们来分析一下,还有没有更好的实现方法,比如达到O(log(n))的。没有。为什么呢?因为为了判断是否有元素重复,在最坏情况下(比如所有元素都是重复的)我们至少要完全遍历这个字符串一遍才能确定,复杂度为O(n),所以,这个问题的最优解是O(n).

  最后让我们来分析对比一下这两个方法的优劣。表面上,单从时间复杂度上来说,算法2无疑是最优的。但是算法2有个问题,是新添加了数据结构LinkedHashMap,所以空间复杂度要高于算法1,这在输入字符串长度不是很大,并且重复度不高,而同时对系统内存资源比较紧俏的情况下来说,算法1有时会比算法2要好一些。因此,在评判一个算法优劣的时候,要结合应用场景来看,而不能单纯从时间复杂度或者空间复杂度上来看。