理论
哈希表理论基础
首先什么是哈希表,哈希表(英文名字为Hash table,国内也有一些算法书籍翻译为散列表,大家看到这两个名称知道都是指hash table就可以了)。
哈希表是根据关键码的值而直接进行访问的数据结构
这么官方的解释可能有点懵,其实直白来讲其实数组就是一张哈希表。
哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素
哈希表能解决什么问题? 一般哈希表都是用来快速判断一个元素是否出现在集合中
哈希函数
哈希函数就是把元素(key)映射为哈希表上的索引,如下图中学生姓名,哈希函数通过hashcode把名字转化为数值,一般hashcode是通过特定的编码方式,可以将其他数据格式转化为不同的数值,这样就把学生姓名转化为哈希表上的索引数字了
哈希碰撞
两个元素(key)通过hashcode编码,取模后都映射到了同一个索引下标,就发生了哈希碰撞
一般哈希碰撞有两种解决方法,拉链法和线形探测法
拉链法
刚刚小王和小李在索引1的位置发生了哈希冲突,发生冲突的元素被存储在链表中,这样就可以通过索引找到小李和小王
其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。
线形探测法
使用线形探测法,一定要保证哈希表的大小 大于 数据量的大小。这种方法需要依靠哈希表的空位来解决问题
做题
242 有效的字幕异位词
使用map来记录每个字母的出现次数,key为字母(rune类型),value为次数
更优的方式:
数组其实就是一个简单的哈希表 这道题的字符串中只有小写字母,那么就可以定义一个数组,来记录字符串中的字符出现的次数。 将数组替换成上一个解法中的map
func isAnagram(s string, t string) bool {
record := [26]int{}
for _, v := range s {
record[v-rune('a')]++
}
for _, v := range t {
record[v-rune('a')]--
}
return record == [26]int{}
}
349两个数组的交集
202 快乐数
1 两数之和
本题其实有四个重点:
- 为什么会想到用哈希表
当我们需要快速判断一个元素是否出现在集合里面的时候,需要考虑哈希表。 这里需要判断在遍历到数字n的时候,判断target-n是否遍历过。
- 哈希表为什么用map
需要存储多个元素,例如数组元素 和 数组下标,使用数组不能快速判断元素是否出现在数组,只有在map的key存储数组元素可以做到
- 本题map是用来存什么的
数组元素和数组下标
- map中的key和value用来存什么的
key:数组元素 value: 数组下标
把这四点想清楚了,本题才算是理解透彻了。
总结
- 总结一下,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。
如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!
- golang中string,byte,rune之间的区别:
byte类型是unit8类型的别称,一个字节,可以表示所有的ASCII码
rune类型是unit32类型的别称,4个字节,一个字节可以表示ASCii中的所有,但是要表示unicode中的别的内容,就得用多个字节了,所以用rune来表示。
- 当使用下标来访问string
使用len()方法求出string的长度,是字符串byte字节的个数
比如len("asd余余余"),返回12, asd各一个字节,余余余各三个字节。
当你用下标访问的时候呢,返回的是uint8,也就是byte
- 当使用range遍历string
当使用range遍历,它是一个一个字符返回的,而不是一个字节
for _, v := range “qwer” {
fmt.Println(reflect.TypeOf(v))
}
输出:
int32
int32
int32
- golang中append的用法
golang中的append函数用于向切片(slice)末尾添加一个或者多个元素,并返回一个新的切片
append(s []T, x ...T) []T
其中,s是一个类型为T的切片,x是要添加到切片末尾的一个或多个元素。append函数返回一个新的切片,包含原始切片和添加的元素。
高级用法:
- 追加切片: 可以将一个切片所有元素追加到另一个切片末尾
a = append(a, b...)
a = append(a, 1, 2, 3)
a = append(a, []int{4, 5, 6})
- 复制切片: 创建一个新的切片复制到另一个切片的元素
b := make([]T, len(a))
copy(b, a)
- 在指定位置插入元素或切片
// 在索引i的位置插入元素x
a = append(a[:i], append([]int{x}, a[i:]...)...)
// 在索引i的位置插入切片[x1, x2]
a = append(a[:i], append([]int{x1, x2}, a[i:]...)...)
// 在索引i的位置插入切片b的所有元素
a = append(a[:i], append(b, a[i:]...)...)
- 删除切片中的元素
// 删除索引i的元素
a = append(a[:i], a[i+1:]...)
// 删除从索引i到索引j之间的元素
a = append(a[:i], a[j+1:]...)
- 扩容切片容量
// 在切片a的末尾添加长度为j的新切片,不包含任何元素
a = append(a, make([]int, j)...)