跟着leetbook刷题--哈希表

概述–设计哈希表

分为set和map
就是根据哈希函数映射到存储的地址,把一个大的集合,映射到一个小的集合
哈希函数 与 碰撞策略 非常重要

题目一 设计哈希set

  • 方法一. 大数组法(这种方法太占地方,太占空间)

  • 方法二.再哈希法 (二维数组)

  • 方法三 使用链表的拉链法
    先使用数组,数组的大小要使用质数
    哈希函数 M%N=r N要选取质数
    M=km N=kn M=qN+r q是商 km=qkn+r ; r=km-qkn; r=k(m-qn) r就是 k的倍数 其取值范围就是
    (0,k,2k,3k,N-1)如果k是2 就是 2 4 6 那中间的 1 3就白白浪费了,所以尽量让k是1 k是公约数,所以N尽量为质数

  • 方法四 开地址法
    如果有冲突,就存入相邻的下一个位置
    hash下划线1(key)就是最原始的key的哈希值 下面的这个7是数组的长度
    开地址法解冲突
    随着不停地往里面放,很快就会产生聚集的现象,都聚集到了一起,哈希冲突的可能性变大了,所以我们可以给hash表扩容,当超过一定的负载因子的时候(hash表里的有效元素/hash表长度),就给hash表扩容
    哈希函数的技巧

  • 当长度是2的次幂的时候(所以每次扩容要两倍两倍的扩),满足: hash(key) & (长度-1); 就 相当于 hash%currentLen ,使用这种算法非常的快,因为是位运算,不需要再取模了,当长度是2的次幂的时候,长度-1的二进制就全部都是1,与hash(key)相与的时候,就只剩剩余的几位了,其中的hash需要按照如下计算

  • 使用右移16位与异或

 int hashCode= (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

是因为当数组长度比较小的时候,hash(key) & (长度-1) hash的高几位都被与的时候与掉了,都是0,这样真正起作用的就是低16位了
有的hashCode的低16位可能是一样的,就增大了哈希冲突的概率,这样把高16位移动到低位,再与原来相异或(相同为0,相异为1),高16位还是没有变,但是低16位就有了高16位的特征,这样哈希冲突概率降低

开地址法要删除的时候,不能直接把找到的元素设置为空,因为它附近可能保存了许多同hash值的元素,你置空了相当于这里就没有冲突了,就会插入重复元素,所以插入的时候,看到这里是-1,也不能插入,必须要继续向后检查有没有重复元素

附加知识点: 从arraylist里边遍历边删除,会抛出concurrentModificationException

public static void removeWayThree(ArrayList<Integer> arrayList) {
    for (Integer value : arrayList) {
        if (value.equals(3)) {//3是要删除的元素
            arrayList.remove(value);
        }
    System.out.println("当前arrayList是"+arrayList.toString());
    }
}

删除的几种姿势
弄成字节码

List a = new ArrayList();
a.add("1");
a.add("2");
Iterator i$ = a.iterator();
do
{
    if(!i$.hasNext())
        break;
    String temp = (String)i$.next();
    if("1".equals(temp))
        a.remove(temp);
} while(true);

可以看到是先调用的hashNext 这个函数里判断了cursor和size相不相等,如果相等就直接退出循环了,所以删除1不会抛异常,注意这里后面调用的remove不是iterator类里的remove 是arrayList里的remove,arrayList里的remove删除之后,只增加了modCount,expectedModCount没有增加,下次再到next的时候expectedModCount和modCount就不一样了,就会抛出异常
所以应该使用iterator的remove,Itr是ArrayList的内部类
在这里插入图片描述
ArrayList.this.remove(lastRet)
ArrayList.this是指引用的是外部类的remove

LinkedList里也有内部类ListIterator,里面也有modCount和excpectedModCount

只出现一次的数字

自己异或自己就是0
0异或自己就是自己
所以只要把所有的数字都异或起来,得到的就是重复数字

两数之和

我当时总是两段式的想法,先把 数值->出现这个数值的索引列表 给维护起来
然后再次遍历数组,如果这个数的对子在这个列表里,那么也未必就找到了符合答案要求的两数
1.有可能key对应的这个列表里有一个索引,而这个索引就是它自己,那不可取
[3,2,5] target=6的3时的情况
2.有可能key对应的列表里有一个索引就是自己 还有一个索引不是自己,那可以取不是自己的那个 [3,3,2] target=6的情况
3.有可能key对应的列表里只有一个索引,不是自己,这个比较理想,直接返回就行
[2,4] target=6的情况
所以要尽量不把自己放进hashmap里放了很麻烦,不能一上来就把自己放到map里
而是如果对子在直接就返回答案,不在的话,存储起来

字符串中的第一个唯一字符

这个题目除了使用hashmap以外,还可以使用队列实现,队列有一个先进先出的特性,很适合用来找出第一个满足某个条件的元素。
使用hashmap你需要遍历两遍,但是使用队列+hashmap你只需要遍历一次,就可以找到,队列的队首就是答案,使用hashmap是为了快速得到字符有没有出现过
遍历字符串,如果hashmap里没有正在遍历的字符,就把字符->索引入map和队列里,如果map里已经有正在遍历的字符了,则把字符对应的索引更新为-1,说明已经重复了,并且把这个已经重复的字符出队,并且把队列中剩余的重复字符都出队,保证队首是非重复的
用到了延时删除的技巧,先标记,下次再删除

存在重复元素2

哈希法:
没有必要维护两个哈希表,还遍历两遍,可以只维护一个长度较小的哈希表,里面放数组里的元素和它出现的次数,这样遍历长度更长的哈希表的时候,可以直接在次数上减,如果减到0了,那就说明长度短的数组已经没有这个元素了,这就是拓展问题的解法

归并排序法
可以先把数组排序,然后用归并排序解

日志速率限制器

可以用一个hashmap解决问题,但是这样日志越来越多,最后把内存撑死了,可以使用队列的思想,把时间已经失效的日志从队列删除,有点类似于滑窗,但是这是一次会使很多元素失效的滑窗

存在重复元素2

思路一: 可以使用哈希表实现
只保存元素的最大索引

思路二: 使用滑动窗口实现
使用滑动窗口的时候没有必要单独写一个for循环,创建窗口
可以使用窗口的最后一个值来进行窗口的移动,并且窗口维护一个hashset,窗口移动的时候,把老的元素从窗口中拿出去,加人新的元素,如果在hashset里,新的元素有重复的,那么就符合题目的要求

字母异位词分组

其实是把各个元素设计一种归一的方式
比如说eat tea都归一化成aet
归一的方式有很多种 比如 排序 但是 这种都是小写字母,可以再进行一次哈希,26个字母表有的话,就设置成出现的次数,就是把元素编码成了 a1b3 这种格式

移位字符串分组

不需要和别的字符串比,字符串A里的每个字符比B里的每个字符的差值都是一样的
,这样的思路没有办法写哈希表的key,要尽量和自己相比较,就是和自己字符串首字符的差值,比如 abc 和 efg 和首字母的差值都是 012 012,首字母的差值一样的话,两个字符串里的字符差值也就一样了
在这里插入图片描述
从b->a 只要走一步 b-a
从a->b要走 整整一圈除去从b->a 也就是 26-(b-a)
为了二者的统一 可以都加上26再%26
(26+ a-b)%26 a更小 相当于 走 整整一圈除去从b->a 也就是 26-(b-a)
(26+b-a)%26 当b大于a的时候

重复的子树

把树序列化以后放到hashmap里去重,重点在把子树压成字符串,不是最优解,因为要拼接上字符那长长的字符串,也需要O(N)的时间,可以把每个子树的子结构都标个id,每个子树就可以表示为<根, 左子树id, 右子树Id> 这样,拼接的时候也是常量时间,整体就只有O(N)的时间复杂度
两个数据结构:
树结构map
3.0.0 -> 1
2,1,0 -> 2
每个子结构出现的次数:
1->2
2->2

注意: 如果用字符串进行压缩,只能选取前序和后序,中序不能
因为没办法区分开,
在这里插入图片描述
字符串表示都是 # 0 # 0 # 无法区分究竟哪个0是根节点

常数时间插入、删除和获取随机元素

想要查找 删除 增加都是O(1)的时间复杂度,查找只有hash表是O(1)的时间复杂度,链表删除需要记录要删除哪个结点,还要保存该结点的前一个结点和后一个结点,不如用数组,删除数组的最后一个元素也是O(1),要删除指定的元素,只要把数组的最后一个元素和指定的元素互换,然后删除最后一个元素就可以了,用哈希表记录元素存储的下标,就知道元素存储的地址了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值