剑指offer刷题-字符流中第一个不重复的字符(LinkedHashMap)
题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。
用例:
“google”
对应输出应该为:
“ggg#ll”
分析:
- 在前三个字符时,第一个不重复的字符始终是g,因此一直输出g;
- 到第四个字符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:关于集合详解
一、集合体系
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
-
HashMap继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口,底层数据结构为数组(table)+链表(entry),每个entry对应着一个table的索引页,对应着一个唯一的hash地址值
-
key、value都可以为null,其中HashMap最多只允许一条记录的键为Null,允许多条记录的值为Null。此外,HashMap中的映射不是有序的。
-
HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
-
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
-
HashMap不支持线程的同步(即任一时刻可以有多个线程同时写HashMap),可能会导致数据的不一致,因此线程不安全。如果需要同步,可以用ConcurrentHashMap。
-
遍历速度和他的容量有关。
(2)HashTable
-
与 HashMap类似,它继承自Dictionary类。
-
Hashtable不允许记录的键或者值为空;
-
它支持线程同步,线程安全(即任一时刻只有一个线程能写Hashtable),性能开销较大。
(3)LinkedHashMap
-
拥有 HashMap 的所有特性,比 HashMap 多维护了一个双向链表,所以保存记录插入顺序;
-
遍历比HashMap慢。不过有种情况例外:当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢。因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。
(4)TreeMap
- TreeMap 的底层就是一颗红黑树,并且它是按照 key 的自然顺序(或者指定排序)排列。
- 它的 containsKey , get , put和remove 方法的时间复杂度是 log(n) .
- TreeMap实现SortMap接口.
(5)ConcurrentHashMap
- ConcurrentHashmap区别于hash的分段锁
- 使用Segment同步方法,而且Segment又继承了ReentrantLock,所以实现了分布式锁,保证线程安全的同时还提高了效率。
3、主要实现类区别总结
- Vector和ArrayList
Vector | ArrayList |
---|---|
基于数组 | 基于数组 |
线程安全 | 线程不安全 |
超出长度时,扩为两倍,因此 适用于数据量较大 | 超出长度时,扩为1.5倍 |
索引数据快,插入数据慢 | 索引数据快,插入数据慢 |
- arraylist和linkedlist
arraylist | linkedlist |
---|---|
基于动态数组 | 基于链表 |
检索快,添加/删除慢 | 检索慢,添加/删除快 |
- HashMap与TreeMap
HashMap | TreeMap |
---|---|
通过hashcode快速查找 | |
排列顺序是不固定的 | 自然顺序 |
4)HashTable 和HashMap
HashTable | HashMap |
---|---|
线程安全 | 线程不安全 |
允许一个key为空,多个Value为空 | Key和value均不允许为空 |
推荐阅读
-
剑指offer JZ54 字符流中第一个不重复的字符 Python 多解
-
剑指offer54:字符流中第一个不重复的字符
-
剑指offer:字符流中第一个不重复的字符
-
剑指Offer——JZ54.字符流中第一个不重复的字符【哈希+队列】
-
剑指offer---字符流中第一个不重复的字符(Java)
-
剑指offer刷题-字符流中第一个不重复的字符(LinkedHashMap)
-
(Java 剑指 offer)字符流中第一个不重复的字符
-
剑指Offer58-字符流中第一个不重复的字符
-
剑指offer JZ54 字符流中第一个不重复的字符 Python 多解
-
剑指offer:Python 字符流中第一个不重复的字符