代码随想录训练营day06|哈希表part06

哈希表

哈希表是根据关键码的值而直接进行访问的数据结构。通俗讲,就是根据索引直接访问结构中的数据。一般来哈希表用来判断一个元素是否出现在集合里。将数据映射到哈希表上是由哈希函数完成

哈希函数

                                                                                                                                 @橘子洲啊

 哈希函数如上图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把数据映射为哈希表上的索引数字了。如果hashcode得到的数值大于哈希表大小,对其做一个取模操作,保证得到的数值不大于tableSize。

哈希碰撞

上图中,两个数据同时被哈希函数映射至一个索引,这一现象称为哈希碰撞

原因:哈希函数的输入空间远大于其输出空间,因此存在多个不同的输入映射到相同的哈希值的可能。

一般有如下几种解决办法:

1、开放地址法
(1)线性探测

索引已经存在,则在原有索引基础上后移一位,直至不发生哈希冲突

图片来源 @ DXXME

(2)再平方探测

按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。
要注意平方不能超过容量的值
Size=16的时候,找备选的单元只能取i=1,2,3,也就是距离冲突单元1,4,9个单位的位置了。

(3)伪随机探测

按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值的基础上加上随机数,直至不发生哈希冲突。

2、链式地址法(HashMap的哈希冲突解决方法)

对于相同的值,使用链表进行连接。使用数组存储每一个链表。
就是hashmap的底层原理 :数组+链表 就是没有红黑树

优点:
(1)拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
(2)由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
(3)开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
(4)在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。
缺点:
(1) 指针占用较大空间时,会造成空间浪费,若空间用于增大散列表规模进而提高开放地址法的效率。

3、再哈希法

对于冲突的哈希值再次进行哈希处理,直至没有哈希冲突。
上述哈希碰撞相关内容来源于博主@DXXME

242. 有效的字母异位词

242. 有效的字母异位词

此题比较简单,直观的感受就是计算每个字符串中字母出现的个数,然后判断两个字符串每个字母出现次数是否相同。完全符合哈希表的使用规律

func isAnagram(s string, t string) bool {
	var s_Length map[string]int = make(map[string]int)
	var t_Length map[string]int = make(map[string]int)
	// len获取的是字符的字节长度,两种方法计算字符长度:utf8.RuneCountInString()
	// len([]rune(s))
	// s[i]取出的是字符对应的utf8编码
	if len([]rune(s)) == len([]rune(t)) {
		fmt.Printf("%d\n", len(s))
		for i := 0; i < len([]rune(s)); i++ {
			s_Length[string([]rune(s)[i])]++
			t_Length[string(t[i])]++
		}

		for n := 0; n < len([]rune(s)); n++ {
			if s_Length[string(s[n])] != t_Length[string(s[n])] {
				return false
			}
		}
		return true
	}
	return false
}

此处对Go语言操作字符串进行一个小总结。

1、计算字符串的长度

go中string底层是通过byte数组实现的,使用len(str)方法实际是在计算字节长度。对于len()函数,获取的是字符的字节长度,而不是字符长度。Go中有两个方法能计算字符长度:utf8.RuneCountInString()[ ]rune()方法。前者直接计算字符长度,后者是将字符转换成字符数组,再配合len()函数使用。

2、获取字符串中的字符

直接使用索引方式访问返回的是字符的utf8编码

3、Go的字符类型

byte(uint8):代表ASCII码的一个字符

rune(int32):代表一个UTF-8字符,当处理中文等其他符合字符时,需要使用该类型

由于此处直接使用两个map类型,需要占用大量的空间,对于此题目的优化方式,直接创建一个空间大小为26的数组,记录每个字母出现的次数。下列代码来源代码随想录

func isAnagram(s string, t string) bool {
	s_num := [26]int{}
	for _, n := range s {
		fmt.Println(n)
		s_num[n-rune('a')]++
	}

	for _, n := range t {
		s_num[n-rune('a')]--
	}
	return s_num == [26]int{} //判断其是否减为0
}

349. 两个数组的交集 

349. 两个数组的交集

此题也比较简单,只需要一个数组中的出现过的数存入map中,再判断另外一个数组中的元素在map中是否存在,存在就表示是两个数组的交集。但是这题有个小坑:如果遇到重复的元素,都会存入result数组中。解决方法:1、每次匹配成功后,需要删除map中该元素。2、使用set,但是Go原生数据结构中,没有set数据结构,只有第三方包("github.com/deckarep/golang-set")有实现,后续可以研究一下这个包。

func intersection(nums1 []int, nums2 []int) []int {
	var map1 map[int]int = make(map[int]int)
	// 动态数组
	var result []int
	for _, n := range nums1 {
		map1[n]++
	}
	for _, n := range nums2 {
		if _, ok := map1[n]; ok {
			result = append(result, n)
			delete(map1, n)
		}
	}
	return result
}

202.快乐数

快乐数

emm,快乐数有点不快乐。没做过这题真的很难想到如何利用map来AC。最开始一直想着硬算,一路算下去,直到算到1结束。但是这样没有办法判断fasle的情况如何停止。后来发现,如果算到中间出现了之前出现过的值,后面继续算下去也没有意义。这下就可以用到map的特性了,把每次计算的结果存入hash,当出现重复时便返回false即可

func isHappy(n int) bool {
	map1 := make(map[int]int)
	for {
		n = getSum(n)
		if n == 1 {
			return true
		}
		if map1[n] > 0 {
			return false
		} else {
			map1[n]++
		}
	}
}

1. 两数之和

两数之和

这个题目还挺经典的,是使用map解决hash问题的典型案例。这个题目不是那么直接可以用map解决的问题。val存元素索引,key保存nums[i]即可。然后再判断target-nums[i]是否在map中,存在则返回两者的索引

func twoSum(nums []int, target int) []int {
	var result []int
	map1 := make(map[int]int)
	for i := 0; i < len(nums); i++ {
		if map1[target-nums[i]] > 0 {
			result = append(result, i)
			result = append(result, map1[target-nums[i]]-1)
		} else {
			map1[nums[i]] = i + 1
		}
	}
	return result
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值