Algorithm3---search(散列)

本文深入探讨散列技术原理,包括散列冲突解决、散列函数设计及其实现,同时精选LeetCode上经典算法题如两数之和、无重复字符的最长子串、三数之和等,提供详细的解析与Go语言代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

散列

理想散列表是一个包含关键字的具有固定大小的数组,它能够以常熟时间执行插入,删除和查找操作。

  • 映射规则叫散列函数
  • 散列冲突
  • hasmap的默认初始长度是16,并且每次自动扩展或是手动初始化时,长度必须是2的幂(为了均匀分布)
  • hashcode取模的方式固然简单,但效率很低,发明者采用了位运算的方式
  • Hashmap在插入元素过多的时候需要进行Resize,Resize的条件是:HashMap.Size >= Capacity * LoadFactor(0.75)
  • Resize包含扩容和ReHash两个步骤,ReHash在并发的情况下可能会形成链表环
  • 并发环境下,如何兼顾线程安全运行效率呢? ConcurrentHashMap
  • ConcurrentHashMap是一个二级哈希表。在一个总的哈希表下面,有若干个子哈希表,采用了锁分段技术。

冲突解决

  • 拉链发
    将同一个值的关键字保存在同一个表中,查找的时候,除了根据计算出来的散列值找到对应位置外,还需要在链表上进行搜索。而在单链表上的查找速度是很慢的
  • 开放定址法
    如果冲突发生,就选择另外一个可用的位置,无论是哪种开放定址法,它都要求表足够大:
    1、线性探测法,可能造成一次聚集
    2、平方探测法,可能产生二次聚集
    3、双散列,hash1(X)+2hash2(X)
  • 再散列
    如果插入新的数据时散列表已满,或者散列表所剩容量不多该怎么办?这个时候就需要再散列

应用

  • 文件校验
  • 数字签名

两数之和(LeetCode 上第 1 号问题)

题目描述

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

题目解析

使用散列表来解决该问题。
首先设置一个map容器用来记录元素的值与索引,然后遍历数组nums。

  • 每次遍历时使用临时变量 tmp 用来保存目标值与当前值的差值
  • 在此次遍历中查找 record,查看是否有与 tmp 一致的值,如果查找成功则返回查找值的索引值与当前变量的值。
  • 如果未找到,则再 record 保存该元素与索引值 i

代码实现

func TwoSum(nums []int, t int)(int,int){
	record := make(map[int]int)
	for i:=0;i<len(nums);i++{
		tmp := t - nums[i]
		j,ok := record[tmp]
		if ok{
			return i,j
		}
		record[nums[i]] = i
	}
	return -1,-1
}

无重复字符的最长字串( LeetCode 上第 3 号问题)

题目描述

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

题目解析

建立一个HashMap,建立每个字符和其最后出现位置之间的映射,然后再定义两个变量res和left。

  • res 用来记录最长无重复字串的长度
  • left 指向该无重复子串左边的起始位置的前一个
    接下来遍历整个字符串,对于每个遍历到的字符,如果该字符已经在HashMap中存在了,并且如果其映射值大于 left 的话,那么更新 left 为当前映射值,然后映射值更新为当前坐标 i ,这样保证了 left 始终为当前边界的前一个位置,然后计算窗口长度的时候,直接用 i-left 即可, 用来更新结果 res。

代码更新

//利用滑动窗口
func LengthOfLongestSubstring(s string)int{
	start,end := 0,0; res := 0;n := len(s)
	//借助一个容器 来判断子串中是否有重复
	m := map[byte]byte{}
	for start < n && end < end{
		if _,ok := m[s[end]];!ok{
			m[s[end]] = s[end]
			end++ //移动滑动窗口
			res = int(math.Max(float64(res),float64(end-start)))
		}else{
				//有重复的,滑动窗口移动
				delete(m,s[start])
				start++
		}
	}
	return res
}
优化滑动窗口
func LengthOfLongestSubstring(s string)int{
	res := 0; left := -1; n := len(s)
	m := map[string]int{}
	for i := 0; i<n; i++{
		if _,ok := m[string(s[i])];ok{
			//重复的,
			left = int(math.Max(float64(m[string(s[i])]),float64(left)))
		}
		res = int(math.Max(float64(res),float64(i-left)))
		m[string(s[i])] = i
	}
	return res
}

三数之和( LeetCode 上第 15 号问题)

题目描述

给定一个包含 n 个整数的数组 nums, 判断 nums 中是否存在三个元素a, b, c,使得a+b+c=0? 找出所有满足条件且不重复的三元组。

题目解析

一开始可以选择一个数,然后再去找另外两个数,这样只要找到两个数且和为第一个选择的数的相反数就行了。也就是需要枚举a 和 b, 将 c 存入map即可。

代码实现

func ThreeSum(nums []int)[][]int{
	sort.Ints(nums)
	n := len(nums)
	result := [][]int{}
	for i:=0;i<n-1 && nums[i]<=0; i++{
		if i>0 && nums[i] == nums[i-1]{continue}
		// j start指针, k end指针
		j := i+1; k := n-1
		t := 0-nums[i]
		for j<k{
			sum := nums[j] + nums[k]
			if sum == t{
				result = append(result,[]int{nums[i],nums[j],nums[k]})
				j++ //右移
				for j<k && nums[j-1] == nums[j]{
					j++
				}
				k-- //左移
				for j<k && nums[k+1] == nums[k]{
					k--
				}
			}else if sum>t{
				k--
				for j<k && nums[k+1] == nums[k]{
					k--
				}
			}else{
				j++
				for j<k && nums[j-1] == nums[j]{
					j++
				}
			}
		}
	}
	return result
}

最接近的三数之和( LeetCode 上第 16 号问题)

题目描述

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

代码实现

import (
	"math"
	"sort"
)
func ThreeSumClosest(nums []int,t int)int{
	sort.Ints(nums)
	res := nums[0] + nums[1] + nums[2]
	for i:=0; i<len(nums)-2; i++{
		m,n := i+1, len(nums)-1
		for m < n{
			sums := nums[i] + nums[m] + nums[n]
			if math.Abs(float64(sums-t))<math.Abs(float64(res-t)){
				res = sums
			}
			if sums > t{
				n--
			}else if sums < t{
				m++
			}else{
				return sums
			}
		}
	}
	return res
}

重复的DNA序列(LeetCode 上第 187 号问题)

题目描述

所有 DNA 由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。

编写一个函数来查找 DNA 分子中所有出现超过一次的 10 个字母长的序列(子串)。

两个数组的交、并集

题目解析

1、

  • 遍历 num1 ,通过map 记录存储num1 的元素与频率
  • 遍历 num2,在record 中查找是否有相同的元素,如果有,用 result 进行存储,同时该元素的频率减一
    2、
  • 遍历 num1 ,通过 set 记录存储 num1 的元素
  • 遍历 num2 , 在 record 中查找是否有相同的元素,如果有,用 result 进行cun’c

代码实现

func Intersection(m []int,n[]int)(result []int){
	record := make(map[int]int)
	for _,i := range m{
		record[i]++
	}
	for _,j := range n{
		times,_ := record[j]
		if times != 0{
			m = append(result,j)
		}
	}
	return
}

回旋镖的数量(LeetCode 上第 447 号问题)

题目描述

给定平面上 n 对不同的点,“回旋镖” 是由点表示的元组 (i, j, k) ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序)。n < 500

找到所有回旋镖的数量。

题目解析

n 最大为 500,可以使用时间复杂度为 O(n^2)的算法。

  • 遍历所有的点,让每个点作为一个锚点

  • 然后再遍历其他的点,统计和锚点距离相等的点有多少个

  • 然后分别带入 n(n-1) 计算结果并累加到 res 中

  • 如果有一个点a,还有两个点 b 和 c ,如果 ab 和 ac 之间的距离相等,那么就有两种排列方法 abc 和 acb ;

  • 如果有三个点b,c,d 都分别和 a 之间的距离相等,那么有六种排列方法,abc, acb, acd, adc, abd, adb;

  • 如果有 n 个点和点 a 距离相等,那么排列方式为 n(n-1);

代码实现

func Intersection(n[][]int)(result int){
	//存储 i 点到其他点的距离出现的频次
	record := make(map[int]int)
	for i := range n{
		for j := range n{
			if j != i{
				record[dis(n[i],n[j])]++
			}
		}
		for _,iter := range record{
			result += 	iter*(iter-1)
		}
	}
	return 
}

func dis(m []int,n []int)int{
	return (m[0]-n[0])*(m[0]-n[0])+(m[1]-n[1])*(m[1]-n[1])
}

四数相加Ⅱ(LeetCode 上第 454 号问题)

题目描述

给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。

为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -2^28 到 2^28- 1 之间,最终结果不会超过 2^31 - 1 。

题目解析

与 Two Sum 极其类似,使用哈希表来解决问题。

  • 把 A 和 B 的两两之和都求出来,在哈希表中建立两数之和与其出现次数之间的映射;

  • 遍历 C 和 D 中任意两个数之和,只要看哈希表存不存在这两数之和的相反数就行了。

代码实现

func FourSumCount(a []int,b []int,c []int,d []int)int{
	hashtable := make(map[int]int)
	for _,i := range a{
		for _,j := range b{
			hashtable[a[i]+b[j]]++
		}
	}
	res := 0
	for _,i := range c{
		for _,j := range d{
			if _,ok := hashtable[-c[i]-c[j]];ok{
				res++
			}
		}
	}
	return res
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值