字符流中第一个不重复的字符

字符流中第一个不重复的字符

题目描述

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

输出描述:

如果当前字符流没有存在出现一次的字符,返回#字符。

这道题的类初始结构一开始我没看懂:

public class Solution {
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
    
    }
}

其实意思是,每次字符输入流都会调用insert进行输入,然后在不知道什么时候会调用FirstAppearingOnce()输出第一个不重复的字符。这道题的一个点是我们只提供方法供调用,并不知道什么时候调用,所以要处理各种可能的调用情况。


时间复杂度O(1),空间复杂度O(1):无须存储所有字符

这道题目的大致思路其实都差不多,只不过看了许多答案,发现都是存储了所有字符,然后再进行遍历判断
其实并不需要这样,这样子 HR 哪里会给 Offer 呢? 用户 txlstars 的回答和本文的优化相同(绝对不是面向 Ctrl+C 编程的~)

字符出现次数的判断(不重复字符):
这个做法大致相同,利用 Hash 思想采用128大小的计数数组进行计数也好,或者是使用 Map 键值对映射也好,都差不多,使用数组会更简单。

字符出现顺序的判断(第一个字符):
这里就是改进的关键之处了,容易发现,字符流中不重复的字符可能同时存在多个,我们只要把这些 “不重复字符” 保存起来就可以,而无需保存那些重复出现的字符,而为了维护字符出现的顺序,我们使用队列(先进先出)这一结构,先出现的不重复字符先输出:

  • 入队:获取字符流中的一个字符时,当我们判断它是不重复时,将它加入队列;
  • 输出/出队:注意,因为队列中存储的 “不重复字符” 在一系列的流读取操作后,随时有可能改变状态(变重复),所以,队列中的字符不能直接输出,要先进行一次重复判断,如果发现队头字符已经重复了,就将它移出队列并判断新的队头,否则,输出队头的值;
import java.util.Queue;
import java.util.LinkedList;
import java.lang.Character;

public class Solution {
   int[] charCnt = new int[128];
   Queue<Character> queue = new LinkedList<Character>();

   //Insert one char from stringstream
   public void Insert(char ch) {
       if (charCnt[ch]++ == 0) //新来的单身字符,入队
           queue.add(ch);
   }
   //return the first appearence once char in current stringstream
   public char FirstAppearingOnce() {
       Character CHAR = null;
       char c = 0;
       //这里只能查看,不能弹出来,因为可能要多次查看这一个字符,只有判断它不再是不重复字符才能弹出来
       while ((CHAR = queue.peek()) != null) {
           c = CHAR.charValue();
           if (charCnt[c] == 1) //判断是否脱单了,没脱单则输出
               return c;
           else queue.remove(); //脱单了就移出队列,它不会再回来了
       }
       return '#'; //队空,返回#
   }
}

这道题的解法作者说的时间和空间都是O(1)的,想一想确实是这样。先看空间,charCnt 的大小是确定的,queue中存放的数量也不会超过128(因为一旦重复就不会入队列了,所以最多把128个都放进去)。时间的话,insert是O(1)的,而FirstAppearingOnce最多每次把queue都查一遍,但是queue大小的上限是确定的,所以它的时间消耗上限也是确定的。

很巧妙的思路,它的主要优化是在,我们如果直接使用哈希表的话,最后会遇到查找当前第一个只出现一次的字符的问题,这如果直接做的话只能遍历字符串,那这就是O(n)的了,但是这里使用一个队列将曾经的第一个只出现一次的字符都存起来,调用的时候直接检验有效性。因为这个队列和哈希都是依赖于元素的有限性的,所以哈希能用的地方这个队列都可以用,挺好的方法。

另外,我们默认这里说的字符就是ASCII码,一共128个,值从0到127.否则没办法算了,UTF-8一共有一百多万个


  1. java的char和int也是可以直接转换的

  2. 一开始我写的代码是这样:

    while(hash[te]>1 &&!firstChar.isEmpty()){
                firstChar.poll();
                    te=firstChar.peek();
            }
    

    这里就有个问题,比如队列只有一共元素,那poll之后再peek就是空指针了,所以应该改写为这样,在poll之后再判断是否为空,最一开始也先判断一下是否为空:

    if(firstChar.isEmpty()){
                return '#';
            }
            Character te=firstChar.peek();
            while(hash[te]>1){
                firstChar.poll();
                if(!firstChar.isEmpty())
                    te=firstChar.peek();
                else
                    return '#';
            }
    
posted @ 2020-03-07 23:21  别再闹了  阅读(129)  评论(0)    收藏  举报