剑指Offer-56-字符流中第一个不重复的字符

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dawn_after_dark/article/details/82531520

项目地址:https://github.com/SpecialYy/Sword-Means-Offer

题目

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

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

解析

预备知识

看到统计字符相关的题目,可以首选哈希表的思想,也就是说字符作为key,出现次数作为value,这样我们就可以牺牲空间换来O(1)的访问。一般适用于键值较少的情况,否则可能会超出内存限制。
因为哈希表的底层结构其实就是数组,键值对的key通过hash函数计算的结果在数组索引范围内进行寻址,也就是说通过哈希函数可以把key转化为数字,且数字范围为数组的大小。
又因为字符的ascii码就是整型数字且唯一表示字符,因此我们可以直接把字符直接作为数组索引,那么出现的次数就是数组中元素值。

思路一

有了预备知识对哈希表原理的解释,我们可以利用字符的特性,使用数组作为我们简单map容器。
我们申请一个2的15次方大的数组(因为Java中char用2个字节实现,其他语言的话可以自行选择)来作为map对字符的出现次数进行统计,然后申请一个字符串缓冲器StringBuilder来记录插入的字符,为了防止缓冲器容量爆炸,我们在插入时判断如果该字符已经出现过,就没必要放入了,因为我们要输出的第一个不重复的字符。
在我们对StringBuilder遍历求出现1次的字符的时候,可以顺便删除已经重复的字符,避免下次不必要的查找,类似的优化可以体现在并查集中查找祖先的过程

    //-------------方法一-------------
    //Java中采用双字节存储char类型,最多有2的15次方个
    static int MAX = 1 << 15;
    //记录字符出现的次数
    int[] visited = new int[MAX];
    //保持插入字符的顺序
    StringBuilder sb = new StringBuilder();

    public void Insert(char ch) {
        if(visited[ch] == 0) {
            sb.append(ch);
        }
        visited[ch]++;
    }

    public char FirstAppearingOnce() {
        for(int i = 0; i < sb.length(); i++) {
            if(visited[sb.charAt(i)] == 1) {
                return sb.charAt(i);
            }else if(visited[sb.charAt(i)] > 1){
                sb.delete(0, 1);
                //注意此处i要后退一下,因为删除操作会涉及数组左移
                //也就是原本i+1的元素会左移到i位置上
                //迭代器中安全删除也是同样的处理
                i -= 1;
            }
        }
        return '#';
    }

思路二

思路一中我们利用额外的字符串缓冲器来记录字符出现顺序,能不能只利用map数组就能解决问题呢?
在剑指Offer一书中给出了这样的答案,采用的是多状态和一个全局的顺序索引index来优化的。
书中规定未访问的元素在map中对应的value为-1,当访问一个元素,如果该元素对应的value为-1,表示这是该元素第一次出现,value记为出现次序index。
如果该元素的值不为-1,表明之前出现过,那么我们把它对应的value设置为-2。
在查找第一个不重复的字符时,我们遍历一遍map数组,找出其中value值表示次序的且次序最小的那个字符。


    static int MAX = 1 << 15;
    int[] count = new int[MAX];
    int index = 0;

    //初始块代码,Java特性,表明对象初始化时会执行
    {
        Arrays.fill(count, -1);
    }

    //--------------方法二--------------
    public void Insert2(char ch) {
        if(count[ch] == -1) {
            count[ch] = (index++);
        }else if(count[ch] >= 0) {
            count[ch] = -2;
        }
    }

    public char FirstAppearingOnce2() {
        char ch = '#';
        int minIndex = Integer.MAX_VALUE;
        for(int i = 0; i < MAX; i++) {
            if(count[i] >= 0 && count[i] < minIndex) {
                ch = (char) i;
                minIndex = count[i];
            }
        }
        return ch;
    }

总结

字符类的关于次数问题,可以采用空间换时间的做法。

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/82531520