《剑指offer》 第50.2题 字符流中第一个不重复的字符

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

https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。


  看到出现1次,第一想法肯定是使用基于Hash表的方法,如果不用库函数的话,那么就得使用数组,而使用一个数组的时候,就和Hash表一样,索引需要有独特的含义。其实也就是需要自己实现一个和Hash类似功能的数组。当然可以有其他的一些变种的写法,但大致思想相同。

解法1:

  只使用一个数组,数组中不仅可以判断是否出现了多次,并且在出现一次时,数组中存放字符在字符流中出现第一次的位置,而数组索引和字符的ASCII码对应起来,由于ASCII码有128个字符,因此数组大小为128即可。
  将数组初值赋值-1,某个字符第一次出现时,将-1更新成当前字符在字符流中的索引(位置)。第二次出现时,将之前赋值的索引擦除,并且不用进入后续的判断,这里选择赋值成-2(赋值情况不唯一)。也就是说,没出现过,数组记录-1,出现一次,数组记录位置,出现多次,数组记录成-2.
  而在FirstAppearingOnce方法中,我们要遍历数组找到正确的解,所以我们要做两件事,第1是找到所有出现1次的字符,由于要求我们返回第一次出现一次的字符,所以第2件事就是比较各个出现1次的字符在字符流中的位置,我们要找到最先出现的那个字符即位置最小的字符。因此还需要设置一个变量,该变量记录某个字符的位置,在找到下一个出现一次的字符时,比较这两个只出现1次的字符的位置,看谁更靠前,就要谁。(可以把这一过程理解成,在某个数组中找到最小的那个数,这个数就是第一个出现1次的字符的索引)
  最妙的是,这个方法只利用一个数组,就完成了记录某个字符是否出现了一次,以及出现一次时的在字符流的位置。

public class Solution {
    private int index = 0;//index用于记录某个字符出现的位置
    private int[] arr = new int[128];
    public Solution(){  //构造函数初始化数组为-1,
        for(int i=0;i<128;i++) 
            arr[i]=-1;
    }

    public void Insert(char ch) {
        if(arr[ch]==-1) {//arr[ch]和arr[(int)ch]是一样的
            arr[ch]=index;   //第一次出现时,记录其在字符流中的位置
        }else if(arr[ch]>=0) {//大于0,说明某个字符出现过了
            arr[ch]=-2;     //多次出现时,重置
        }
        index++;
    }

    public char FirstAppearingOnce() {
        int minIndex = Integer.MAX_VALUE;  //方便比较出最靠前的那个出现1次的字符
        char ch='#';
        for(int i=0;i<128;i++) {
            if(arr[i]>=0 && arr[i] < minIndex) {//字符赋值给ch,位置赋值给minIndex
                ch = (char) i;
                minIndex = arr[i];
            }
        }
        return ch;
    }
}

解法2:

  之前是一个数组又可以记录索引信息,又可以记录是否出现了多次,用两个数组,将表达的信息分解,所以代码看起来没有解法1那么绕。并且还和解法1不同的是,index记录的不再是第一次出现的位置,而是记录某个字符最后出现的位置,但其实没什么影响,因为我们也用count记录次数,出现1次的字符,记录的位置仍然正确(只出现1次的位置既是第一次出现也是最后一次出现)

public class Solution {
    int[] count = new int[128]; // 字符出现的次数
    int[] index = new int[128]; // 字符出现的位置
    int number = 0;//用于标记字符流的位置

    public void Insert(char ch) {
        count[ch]++;
        index[ch] = number++;
    }

    public char FirstAppearingOnce() {
        int minIndex = number;
        char ch = '#';
        for (int i = 0; i < 128; i++) { 
            if (count[i] == 1 && index[i] < minIndex) {
                ch = (char) i;
                minIndex = index[i];
            }
        }
        return ch;
    }
}

解法3:

  解法1是仿Hash,现在使用真Hash。还有其他方法,就不总结了,还是那句话,在面试中,考察的是思维,少使用工具类,在笔试中,是为了解题,怎么方便怎么来。

import java.util.Map;
import java.util.LinkedHashMap;
public class Solution {
    Map<Character,Integer> map = new LinkedHashMap<>();

    public void Insert(char ch){
        if(map.containsKey(ch))
            map.put(ch,map.get(ch)+1);//value记录的是次数
        else
            map.put(ch,1);//第一次出现
    }

    public char FirstAppearingOnce() {
        for(char ch:map.keySet()){ //遍历,找到出现次数为1的
            if(map.get(ch)==1)
                return ch;
//由于LinkedHashMap是双向链表记录了顺序,所以在遍历中,找到的第一个为1的,就是答案。
        }
        return '#';
    }
}

刷刷题

全部评论

相关推荐

最近群里有很多同学找我看简历,问问题,主要就是集中在明年三月份的暑期,我暑期还能进大厂嘛?我接下来该怎么做?对于我来说,我对于双非找实习的一个暴论就是title永远大于业务,你在大厂随随便便做点慢SQL治理加个索引,可能就能影响几千人,在小厂你从零到一搭建的系统可能只有几十个人在使用,量级是不一样的。对双非来说,最难的就是约面,怎么才能被大厂约面试?首先这需要一点运气,另外你也需要好的实习带给你的背书。有很多双非的同学在一些外包小厂待了四五个月,这样的产出有什么用呢?工厂的可视化大屏业务很广泛?产出无疑是重要的,但是得当你的实习公司到了一定的档次之后,比如你想走后端,那么中厂后端和大厂测开的选择,你可以选择中厂后端(注意,这里的中厂也得是一些人都知道的,比如哈啰,得物,b站之类,不是说人数超过500就叫中厂),只有这个时候你再去好好关注你的产出,要不就无脑大厂就完了。很多双非同学的误区就在这里,找到一份实习之后,就认为自己达到了阶段性的任务,根本不再投递简历,也不再提升自己,玩了几个月之后,美其名曰沉淀产出,真正的好产出能有多少呢?而实际上双非同学的第一份实习大部分都是工厂外包和政府外包!根本无产出可写😡😡😡!到了最后才发现晚了,所以对双非同学来说,不要放过任何一个从小到中,从中到大的机会,你得先有好的平台与title之后再考虑你的产出!因为那样你才将将能过了HR初筛!我认识一个双非同学,从浪潮到海康,每一段都呆不久,因为他在不断的投递和提升自己,最后去了美团,这才是双非应该做的,而我相信大部分的双非同学,在找到浪潮的那一刻就再也不会看八股,写算法,也不会打开ssob了,这才是你跟别人的差距。
迷茫的大四🐶:我也这样认为,title永远第一,只有名气大,才有人愿意了解你的简历
双非本科求职如何逆袭
点赞 评论 收藏
分享
评论
24
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务