题目
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
1 | 输入: ["eat", "tea", "tan", "ate", "nat", "bat"], |
说明:
- 所有输入均为小写字母。
- 不考虑答案输出的顺序。
问题分析
题目中可以看出,我们要找出满足某一种条件的字符串集合,这个条件是:字母全对,只是错位
针对这种情况,我们可以利用算法,先将字符串排序,再比较(排序之后的两个错位字符串会变成一样的)。
或者统计每个字符串所出现字母次数,放入哈希表进行比较。
方法一:排序数组分类
- 思路
当且仅当它们的排序字符串相等时,两个字符串是字母异位词。
- 算法
维护一个映射 ans : {String -> List},其中每个键 $K$ 是一个排序字符串,每个值是初始输入的字符串列表,排序后等于 $K$。
在 Java 中,我们将键存储为字符串,例如,code。
方法二:计数分类
1.思路
当且仅当它们的字符计数(每个字符的出现次数)相同时,两个字符串是字母异位词。
- 算法
我们可以将每个字符串 $\text{s}$ 转换为字符数 $\text{count}$,由26个非负整数组成,表示 $ \text{a} $,$\text{b}$,$\text{c}$ 的数量等。我们使用这些计数作为哈希映射的基础。
在 Java 中,我们的字符数 count 的散列化表示将是一个用 # 字符分隔的字符串。 例如,abbccc 将表示为 #1#2#3#0#0#0 …#0(表示 a 出现了1次, b 出现了2次),其中总共有26个条目。。
代码-排序
1 | class Solution { |
代码-计数
1 | class Solution { |
哈希表相关操作
哈希表实现原理
关于hash的具体实现规则,Java源代码请看之前Java基础集合篇。
Hash家族
Hashtable 是一个包含单向链表的二维数组,其数据结构的数组中是 Entry<K,V> 存储, entry 对象。 Hashtable 有洁癖,不允许存入其中的 key 或者 value 为 null。Hashtable 是线程安全的,所有的方法均用 synchronized 修饰,这样在任一时刻,只有一个线程可以写 Hashtable,因此,对于频繁写操作的业务逻辑,速度会非常慢。
HashMap 是最常用的 Map 型数据结构,它根据键的 hashCode() 值存储数据。HashMap 允许一个 key 为 null ,允许多个 value 为空,HashMap 不支持线程的同步,即可能会出现在同一时刻有多个线程同时写 HashMap ,会产生数据的不一致。如果在修改代码的过程中,需要给 HashMap 限制为线程同步的,可以采用
Collections.synchronizedMap(map);
方法使得HashMap 可以同步。ConcurrentHashMap是基于这样的考虑:降低锁的粒度。在 Hashtable 中的关键字是使用 synchronized 基于整张表结构的,锁的粒度太大,它每次通过锁住整张表让线程独占,来保证安全性。
LinkedHashMap 保存了记录的插入顺序,在使用 Iterator 遍历 LinkedHashMap 的时候,先得到的记录肯定是先插入的。在遍历的时候会比 HashMap 慢,因为 HashMap 是以O(1)来设计存取的。并且 LinkedHashMap 继承自 HashMap ,拥有它的全部特性。
TreeMap是基于红黑树实现的,它是一种有序的存储结构,并且程序员可以自己定义排序器。TreeMap 默认会按存入的键值 key 来排序,默认是按升序排序,当然也可以指定排序的比较器。 TreeMap 同样有洁癖,不允许存入 null 值。使用 Iterator 遍历出来的 TreeMap 往往是有序的。
总结:常用HashMap,允许null插入;有两个子类:ConcurrentHashMap和LinkedHashMap。前者用来弥补线程安全,后者用来弥补有序。此外还有Hashtable和TreeMap。虽然CouncurrentHashMap性能明显优于Hashtable,但是并不能完全取代Hashtable,因为遍历ConcurrentHashMap的迭代器是弱一致的。TreeMap数据结构则可以帮助我们得到一个有序的结果,适用于需要输出排序结果的场景。
注意:Java 8 改进特性
Java 8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,所以其由 数组+链表+红黑树 组成。在 Java 8 中,当链表中的元素超过了 8 个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。
几个常用hash函数
一般做题的时候,通常是用 HashMap 解决问题。
- 创建一个hashMap
1 | Map<String, List> ans = new HashMap<String, List>(); |
在尖括号里,第一个类型是 KEY
, 第二个类型是 VALUE
。
- 判断元素是否存在
1 | boolean containsKey(Object key) |
- 根据 key 找 value
1 | V get(Object key) |
- 添加一对新的键值组合
1 | V put(K key, V value) |
- 删除对应的key
1 | V remove(Object key) |
- 其他函数
1 | void clear() |
hashmap应用场景
在实际应用中,hashMap数据结构 适用以下几个情况
- 需要遍历许多次才能解决的问题,hashMap 可以大大减小算法的时间复杂度
- 需要记忆性的问题,像这道题,我们需要记住每个字符的所属情况,并给他们打一个专属标记。
更多关于哈希表的问题
更多关于哈希表的问题请转到链表标签