疑问:不清楚map和set的区别
map的value可以是任意类型
set的value只能是bool型
思路:
要找两数之和等于目标值 等价于 找target-当前值是否存在于哈希表之中
我们在遍历数的同时,有条件的将当前元素插入到哈希表中
因为题目要求要返回下标,所以必须采用map而不是set,key存数值,value存数值对应的下标
源码:
//哈希表解法
func twoSum(nums []int, target int) []int {
mp := make(map[int]int)
for index, val := range nums {
if preindex, ok := mp[target-val]; ok { //在map中寻找与当前元素相加等于target的元素
return []int{preindex, index} //如果存在返回二个元素的下标
} else {
mp[val] = index //如果不存在,将当前元素及其下标加入到map中
}
}
return []int{}
}
思路:
求四数之和(nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0)等于0出现的次数
等价于在哈希表中找0-(nums3[k] + nums4[l])对应的值(表示出现次数)
因为我们可以为 nums1[i] + nums2[j]构建一张哈希表,key存放nums1[i] + nums2[j]的和,value存放对应和出现的次数
步骤:
1、我们构造一张哈希表,key存放a+b的和,value存放a+b和出现的次数(题目要求我们返回有多少个元组,即出现的次数)
2、我们遍历nums1和nums2数组,统计两个数组元素之和,和出现的次数,放在map中
3、定义int变量res,用来统计nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0出现的次数
4、再遍历num3和nums4数组,找到如果键0-(nums3[k] + nums4[l])在map中出现过的话,就用res把map中对应value(即出现的次数)统计出来
5、最后返回统计值res
func fourSumCount(nums1 []int, nums2 []int, nums3 []int, nums4 []int) int {
mp := make(map[int]int)
for _, val1 := range nums1 {
for _, val2 := range nums2 {
mp[val1+val2]++ //统计nums1和nums2元素之和及其对应出现的次数
}
}
res := 0//出现次数不断累加
for _, val3 := range nums3 {
for _, val4 := range nums4 {
res += mp[0-val3-val4] //查哈希表 因为val1 + val2 + val3 + val4=0,所以找到0 - val3 - val4出现的次数,就相当于找到四数之和为0出现的次数
}
}
return res
}
383.赎金信
由小写英文字母组成,很重要!!!
说明范围只有26,可以使用数组作为哈希表(下标作为key)
没必要使用map就不使用map
因为map的空间消耗要比数组大,因为要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!
思路:
用一个长度为26的数组来记录magazine中字母出现的次数(即构建一张哈希表)
然后用ransomNote去验证这个数组是否包含了ransonNote所需要的所有字母
源码:
func canConstruct(ransomNote string, magazine string) bool {
hash := [26]int{}
for _, ch := range magazine {
hash[ch - 'a']++ //构建一张哈希表,统计magazine中字母出现的次数
}
for _, rans := range ransomNote {//检查ransomNote中的每个字母是否都存在于magazine中
hash[rans - 'a']-- //查看magazine中是否有字母可以组成ransomNote
if hash[rans - 'a'] < 0{ //如果maganize中缺了字母,即不能组成
return false
}
}
//for循环结束,说明ransomNote中的每个字母都检查完了,都存在于magazine中,那么我们返回true
return true
}
我的思路: 暴力解法:三层遍历,时间复杂度太高O(n^3)
哈希解法:两层for循环可以确定a和b的数值了,可以使用哈希法来确定 0 - (a + b)是否在数组出现过,但是题目说不可以包含重复的三元组。如果把符合条件的三元组放进vector中,然后再去重,这是非常费时的,但是可以达到O(n^2)(疑问:哈希表的key存的a+b的和,value部分存放什么呢?难道是切片?存着a、b?)
双指针法:首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left定义在i+1的位置上,定义下标right在数组结尾的位置上(相当于找nums[i] + nums[left] + nums[right] = 0)
错误答案:
//错误例子,没有去重 && 和等于0时left和right忘记进行更新
import "sort"
func threeSum(nums []int) [][]int {
//定义一个二维切片,用来存放结果三元组
res := [][]int{}
//首先要对数组进行排序
sort.Ints(nums)
//然后对排序后的数组进行遍历
for i := 0; i < len(nums); i++ {
left := i + 1 //left指向i+1位置的值
right := len(nums) - 1 //right指向数组的最后一个元素
for left != right { //直到左右指针相遇
if nums[i]+nums[left]+nums[right] > 0 {
right-- //和太大,右指针左移
} else if nums[i]+nums[left]+nums[right] < 0 {
left++ //和太小,左指针右移
} else if nums[i]+nums[left]+nums[right] == 0 {
res = append(res, []int{nums[i], nums[left], nums[right]}) //和为0,将该三元组加入结果二维切片中
}
}
}
return res
}
三元组没有去重,题目不仅要求i,j,k互不相等,而且还要不包含重复的三元组
正确答案:
import "sort"
func threeSum(nums []int) [][]int {
res := [][]int{}
sort.Ints(nums)
for i := 0; i < len(nums)-2; i++ {
if i > 0 && nums[i] == nums[i-1] { // 避免重复解
continue
}
left := i + 1
right := len(nums) - 1
for left < right {
sum := nums[i] + nums[left] + nums[right]
if sum == 0 {
res = append(res, []int{nums[i], nums[left], nums[right]})
for left < right && nums[left] == nums[left+1] { // 避免重复解
left++
}
for left < right && nums[right] == nums[right-1] { // 避免重复解
right--
}
left++
right--
} else if sum < 0 {
left++
} else {
right--
}
}
}
return res
}
去重的逻辑思考
主要考虑nums[i],nums[left],nums[right]的去重
nums[i]如果重复了怎么办,nums[i]时nums里遍历的元素,那么应该直接跳过去
但还有一个问题,是判断nums[i]与nums[i+1]是否相同,还是判断nums[i]与nums[i-1]是否相同,一个是比较它的前一个,另一个是比较它的后一个。
如果这样写:
if (nums[i] == nums[i + 1]) { // 去重操作
continue;
}
那我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。
我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的! 所以这里是有两个重复的维度。
那么应该这么写:
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。