欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

剑指offer刷题-字符流中第一个不重复的字符(LinkedHashMap)

程序员文章站 2022-06-17 16:15:56
...

题目描述

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

输出描述:

如果当前字符流没有存在出现一次的字符,返回#字符。
用例:
“google”
对应输出应该为:
“ggg#ll”
分析:

  1. 在前三个字符时,第一个不重复的字符始终是g,因此一直输出g;
  2. 到第四个字符g因为出现的g和o此时均为重复出现,所以输出#,表示不存在只出现一次的字符。
    3)之后输入le,l是第一个只出现一次的字符,所以输出ll。

思路:

有两个函数:1)Insert的输入为字符,输出为空,主要实现字符流的输入,因为字符流不同于字符串,所以需要容器来存储字符流,以便后续进行字符的出现次数查找。

发现有人用HashMap存储字符流,但是由于HashMap是无序存储的,即不会按照输入的顺序存储,而题目要求是找出第一个只出现一次的字符,因此对存储顺序要求是按照输入的顺序。

所以选用LinkedHashMap存储字符流,是按照插入顺序存储的,满足题目要求。

2)FirstAppearingOnce函数用来找到第一个重复出现的字符。

将字符作为LinkedHashMap的键,其出现次数作为值。遍历LinkedHashMap,当存在只出现一次的字符即值为1时,就输出对应的键即字符,否则输出#。

import java.util.*;
public class Solution {
    char c ='#';
    //Insert one char from stringstream
    LinkedHashMap<Character,Integer> map = new LinkedHashMap<Character,Integer>();
    public void Insert(char ch)
    {
        if(map.containsKey(ch))
        {
            //如果哈希表中包含这个字符,则说明已经出现过
            map.put(ch, map.get(ch)+1);
        }
        else
        {
            map.put(ch,1);
        }
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        for(Map.Entry<Character, Integer> entry : map.entrySet()) 
        {
            while((int)entry.getValue() == 1)
            {
                return (char)entry.getKey();
            }
        }
        return c;
    }
}

PS:关于集合详解

一、集合体系

剑指offer刷题-字符流中第一个不重复的字符(LinkedHashMap)
Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。而是让接口Set和List继承Collection接口。

Map接口是Java.util包中的另一个接口,它和Collection接口是相互独立的,都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,但是可以包含相同的value。

所有的集合类都实现了Iterator接口,这是一个用于遍历集合中元素的接口,主要包含以下三种方法:
(1).hasNext()是否还有下一个元素。
(2).next()返回下一个元素。
(3).remove()删除当前元素。

1、单列集合Collection

List和Set的区别

List Set
有序 无序
可重复 不可重复

ArrayList和LinkedList的区别

对比 ArrayList LinkedList
底层数据结构 数组 双向链表
查找效率 有下标,查找快 查找慢
增删效率 增删慢 增删快

Set的类

AbstractSet HashSet TreeSet
无序 无序 二叉排序树,有序
不可重复 不可重复 不可重复

2、双列集合Map

Map集合中存储的是键值对键不能重复,值可以重复。根据键得到值,对Map集合遍历时先得到键的Set集合,对set集合进行遍历,得到相应的值。

HashMap HashTable ConcurrentHashMap TreeMap LinkedHashMap
无序 无序 无序 红黑树,自然顺序 链表结构,有序(插入顺序)
线程不安全 线程安全 线程安全且锁分离 线程不安全

(1)HashMap

  1. HashMap继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口,底层数据结构为数组(table)+链表(entry),每个entry对应着一个table的索引页,对应着一个唯一的hash地址值

  2. key、value都可以为null,其中HashMap最多只允许一条记录的键为Null,允许多条记录的值为Null。此外,HashMap中的映射不是有序的。

  3. HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量是哈希表中的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。

  4. 通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

  5. HashMap不支持线程的同步(即任一时刻可以有多个线程同时写HashMap),可能会导致数据的不一致,因此线程不安全。如果需要同步,可以用ConcurrentHashMap。

  6. 遍历速度和他的容量有关。

(2)HashTable

  1. 与 HashMap类似,它继承自Dictionary类。

  2. Hashtable不允许记录的键或者值为空

  3. 它支持线程同步,线程安全(即任一时刻只有一个线程能写Hashtable),性能开销较大

(3)LinkedHashMap

  1. 拥有 HashMap 的所有特性,比 HashMap 多维护了一个双向链表,所以保存记录插入顺序

  2. 遍历比HashMap慢。不过有种情况例外:当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢。因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

(4)TreeMap

  1. TreeMap 的底层就是一颗红黑树,并且它是按照 key 的自然顺序(或者指定排序)排列。
  2. 它的 containsKey , get , put和remove 方法的时间复杂度是 log(n) .
  3. TreeMap实现SortMap接口.

(5)ConcurrentHashMap

  1. ConcurrentHashmap区别于hash的分段锁
    剑指offer刷题-字符流中第一个不重复的字符(LinkedHashMap)
  2. 使用Segment同步方法,而且Segment又继承了ReentrantLock,所以实现了分布式锁,保证线程安全的同时还提高了效率。

3、主要实现类区别总结

  1. Vector和ArrayList
Vector ArrayList
基于数组 基于数组
线程安全 线程不安全
超出长度时,扩为两倍,因此 适用于数据量较大 超出长度时,扩为1.5倍
索引数据快,插入数据慢 索引数据快,插入数据慢
  1. arraylist和linkedlist
arraylist linkedlist
基于动态数组 基于链表
检索快,添加/删除慢 检索慢,添加/删除快
  1. HashMap与TreeMap
HashMap TreeMap
通过hashcode快速查找
排列顺序是不固定的 自然顺序

4)HashTable 和HashMap

HashTable HashMap
线程安全 线程不安全
允许一个key为空,多个Value为空 Key和value均不允许为空