【剑指Offer】字符流中第一个不重复的字符——三种解法

字符流中第一个不重复的字符——三种解法

题目描述

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

解题思路

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

方法1

只使用一个数组,数组中不仅可以判断是否出现了多次,并且在出现一次时,数组中存放字符在字符流中出现第一次的位置,而数组索引和字符的ASCII码对应起来,由于ASCII码有128个字符,因此数组大小为128即可。

将数组初值赋值为-1,某个字符第一次出现时,将-1更新成当前字符在字符流中的索引(位置)。第二次出现时,将之前赋值的索引擦除,并且不用进入后续的判断,这里选择赋值成-2(赋值情况不唯一)。也就是说,没出现过,数组记录-1,出现一次,数组记录位置,出现
多次,数组记录成-2。

而在FirstAppearingOnce方法中,我们要遍历数组找到正确的解,所以我们要做两件事:第一是找到所有出现1次的字符,由于要求我们返回第一次出现一次的字符,所以第二件事就是比较各个出现一次的字符在字符流中的位置,我们要找到最先出现的那个字符即位置最小的字符。因此还需要设置一个变量,该变量记录某个字符的位置,在找到下一个出现一次的字符时,比较这两个只出现1次的字符的位置,返回最靠前的那个。可以把这个过程理解为,在某个数组中找到最小的那个数,这个数就是第一个出现1次的字符的索引。

最巧妙的是,这个方法只利用了一个数组,就完成了记录某个字符是否出现一次,以及出现一次时在字符流的位置。
代码如下:

public class Solution {
    // 方法1
    private int index = 0;// index用于记录某个字符出现的位置
    private int[] arr = new int[128];

    // 初始化数组,赋值为-1
    public Solution(){  //构造函数初始化数组为-1,
        for(int i=0;i<128;i++)
            arr[i]=-1;
    }
    public void Insert(char ch) {
        // arr[ch]和arr[(int)ch]是一样的
        if (arr[ch] == -1) {
            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那么复杂。和方法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

使用LinkedHashMap<>()实现计数,同时,由于LinkedHashMap是双向链表记录了顺序,所以在遍历中,找到的第一个为1的就是我们所要的。代码如下

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

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

    //return the first appearence once char in current stringstream
    public char FirstAppearingOnce() {
        // 遍历key,找出出现次数为1的
        for (char chr : map.keySet()) {
            if (map.get(chr) == 1) {
                return chr;
            }
        }
        return '#';
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镰刀韭菜

看在我不断努力的份上,支持我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值