当前位置:   article > 正文

代码随想录训练营day07|哈希表part02

代码随想录训练营day07|哈希表part02

454. 四数相加II

题目链接:454. 四数相加 II

这个题目和两数之和的做法非常相似,将前两个变量和后两个变量合并,就和两数之和非常相似。此外,由于本题是在四个数组中做四数相加,因此不需要考虑重复的情况。所以,这里直接考虑使用哈希表来解决这个问题,将A+B的和存入map中,再判断target-(C+D)的值在map中是否存在相应的key,如果存在,需要加上该key的数量(value)

  1. func fourSumCount(nums1 []int, nums2 []int, nums3 []int, nums4 []int) int {
  2. map1 := make(map[int]int)
  3. total := 0
  4. for i := 0; i < len(nums1); i++ {
  5. for j := 0; j < len(nums2); j++ {
  6. sum := nums1[i] + nums2[j]
  7. map1[sum]++
  8. }
  9. }
  10. for i := 0; i < len(nums3); i++ {
  11. for j := 0; j < len(nums4); j++ {
  12. if _, ok := map1[0-(nums3[i]+nums4[j])]; ok {
  13. total += map1[0-(nums3[i]+nums4[j])]
  14. }
  15. }
  16. }
  17. return total
  18. }

383. 赎金信

题目链接:383. 赎金信

这个题目基本没有什么难度,就是判断构成字符串A的字符集合是否是构成字符串B的字符集合的子集。乍一看,直接把字符串B的字符和对应数量当作键值对存在map中,再使用循环遍历字符串A的所有字符,对其进行两个判断:1)当前字符是否存在map中;2)map中当前字符的数量是否足够。

使用hash能够很好的解决该问题,但是我们不能无脑使用哈希表,毕竟哈希表需要耗费大量空间,我们可以针对具体的问题使用数组进行模拟,因为数组具有一个和哈希表一样的特点,就是存取数据所耗费的时间复杂度都是O(1)。因此,根据对题观察,我们发现英文字符的数量是存在上限的,所有直接使用数组模拟map也能很好的解决问题。

  1. func canConstruct(ransomNote string, magazine string) bool {
  2. map1 := make(map[string]int)
  3. magazine_s := []rune(magazine)
  4. ransomNote_s := []rune(ransomNote)
  5. for _, n := range magazine_s {
  6. map1[string(n)]++
  7. }
  8. for _, m := range ransomNote_s {
  9. if _, ok := map1[string(m)]; !ok {
  10. return false
  11. } else {
  12. if map1[string(m)] > 0 {
  13. map1[string(m)]--
  14. } else {
  15. return false
  16. }
  17. }
  18. }
  19. return true
  20. }

下面的使用数组的解法代码来源:代码随想录

  1. func canConstruct(ransomNote string, magazine string) bool {
  2. record := make([]int, 26)
  3. for _, v := range magazine { // 通过record数据记录 magazine里各个字符出现次数
  4. record[v-'a']++
  5. }
  6. for _, v := range ransomNote { // 遍历ransomNote,在record里对应的字符个数做--操作
  7. record[v-'a']--
  8. if record[v-'a'] < 0 { // 如果小于零说明ransomNote里出现的字符,magazine没有
  9. return false
  10. }
  11. }
  12. return true
  13. }

15. 三数之和

题目链接:15. 三数之和

这道题由于惯性思想,直接套用前面四数相加II的想法,无脑套用map来解决,后来发现使用map得出的解会出现很多的重复解,而且这个去重的方法没有想出来,查看代码随想录的代码也没有看懂,所以直接学习代码随想录给出的双指针解法

核心思想可以分为以下几个步骤:

对于A+B+C=0

1、对数组进行排序,这样方便后续利用数组的元素的相对位置信息

2、第一层循环nums[i]用来固定A的位置,去寻找后面BC的位置

3、利用leftright指针,分别指向A的下一个位置和数组的尾部位置

4、第二层循环,限定right > left,当找到一个符合条件的三元组时,两个指针同时移动,否则根据和大于0还是小于0来移动rightleft

上面四个步骤只是大概方法,还有几个细节需要注意:

1、去重

对于A的去重,放在第一层循环里,判断当前位置的元素是否等于上一个元素

  1. // 对第一个元素进行去重,不能使用nums[i] == nums[i+1],否则会错过[-1,-1,2]这组数据
  2. if i > 0 && nums[i] == nums[i-1] {
  3. continue
  4. }

对于BC的去重,则是需要在找到了相应的解之后,再进行去重,否则会遗漏一部分解,例如[0,0,0,0]

  1. res = append(res, []int{nums[i], nums[left], nums[right]})
  2. //停在重复元素的最后一个
  3. for right > left && nums[left] == nums[left+1] {
  4. left++
  5. }
  6. // 停在重复元素的最前一个
  7. for right > left && nums[right] == nums[right-1] {
  8. right--
  9. }

2、剪枝

通过剪枝操作来减少搜索的时间。根据本题,由于数组经过排序,如果固定位置的A处的值大于0那么就没有必要进行搜索了,直接返回即可。这个剪枝操作需要根据题目内容来设计,但是这个思想能够很好的帮主我们减少程序不必要的运行时间。

  1. func threeSum(nums []int) [][]int {
  2. sort.Ints(nums)
  3. res := [][]int{}
  4. if nums[0] > 0 {
  5. return res
  6. }
  7. for i := 0; i < len(nums); i++ {
  8. if nums[i] > 0 {
  9. return res
  10. }
  11. // 对第一个元素进行去重,不能使用nums[i] == nums[i+1],否则会错过[-1,-1,2]这组数据
  12. if i > 0 && nums[i] == nums[i-1] {
  13. continue
  14. }
  15. left := i + 1
  16. right := len(nums) - 1
  17. for right > left {
  18. if nums[i]+nums[left]+nums[right] > 0 {
  19. right--
  20. } else if nums[i]+nums[left]+nums[right] < 0 {
  21. left++
  22. } else {
  23. res = append(res, []int{nums[i], nums[left], nums[right]})
  24. //停在重复元素的最后一个
  25. for right > left && nums[left] == nums[left+1] {
  26. left++
  27. }
  28. // 停在重复元素的最前一个
  29. for right > left && nums[right] == nums[right-1] {
  30. right--
  31. }
  32. right--
  33. left++
  34. }
  35. }
  36. }
  37. return res
  38. }

18. 四数之和

题目来源:18. 四数之和

这个题目和前面三数之和的做法是一样的,但是一些细节方面的处理略微有些不同。

初始思路:和三数之和一样,找固定位置,由于这是四个数,需要利用双指针寻找的话,就得固定前两个位置也就是AB,CD的搜索同样使用leftright指针的模式,然后对A进行剪枝和去重操作。

上面思路写完代码运行之后,发现还有重复的解。后续查看卡哥的思路才发现,我们对AB都做了固定位置的操作,但是只是对A做了去重操作,并没有对B进行去重操作,这样会导致在B位置出现重复值。例如[2,2,2,2,2],target=8. 如果不对B去重,那么会有两个解,分别对应两个位置【0,1,2,4】和【0,2,3,4】。

因此这个地方对B也需要进行去重操作,以此类推,五数之和、六数之和都是如此

去重

  1. if j > i+1 && nums[j] == nums[j-1] {
  2. continue
  3. }

剪枝

对于B的剪枝原理上和A相同,和三数之和又有所不同,三数之和题目里taget是固定的,等于0,而本题中target是动态变化的,因此不能简单的通过一个当前固定值大于0就停止搜索.

1、对于A的剪枝,需要考虑nums[A]大于target的同时,还需要判断nums[A]>0 || target > 0,防止出现target=-10nums[A]=-4,直接跳过搜索。

2、对于B的剪枝,则需要在A的基础上,考虑一下nums[A]+nums[B]>0即可

  1. func fourSum(nums []int, target int) [][]int {
  2. sort.Ints(nums)
  3. res := [][]int{}
  4. for i := 0; i < len(nums); i++ {
  5. //减枝,提高速度,避免不必要的判断
  6. // 前面nums[i] > 0使用用于过滤target<0的情况
  7. if nums[i] > target && (nums[i] > 0 || target > 0) {
  8. return res
  9. }
  10. if i > 0 && nums[i] == nums[i-1] {
  11. continue
  12. }
  13. for j := i + 1; j < len(nums); j++ {
  14. if nums[i]+nums[j] > target && (nums[j] > 0 || target > 0) {
  15. break
  16. }
  17. if j > i+1 && nums[j] == nums[j-1] {
  18. continue
  19. }
  20. left := j + 1
  21. right := len(nums) - 1
  22. for right > left {
  23. //这里不能先去重,要等当前符合条件的四元组加入res之后再去判断重复,否则会漏掉四元组
  24. if nums[i]+nums[j]+nums[left]+nums[right] > target {
  25. right--
  26. } else if nums[i]+nums[j]+nums[left]+nums[right] < target {
  27. left++
  28. } else {
  29. res = append(res, []int{nums[i], nums[j], nums[left], nums[right]})
  30. for right > left && nums[left] == nums[left+1] {
  31. left++
  32. }
  33. for right > left && nums[right] == nums[right-1] {
  34. right--
  35. }
  36. left++
  37. right--
  38. }
  39. }
  40. }
  41. }
  42. return res
  43. }

哈希表总结

关于哈希表的实现方式,在Go语言当中,官方的库里只有数组和map的实现方式,并没有set,只有第三方库的set实现,这是我后续需要去学习的地方。

其次,需要在做题时需要分析何时使用数组,何时使用map。要依据两者数据结构的特点来选择相应的实现方式

最后,对于Go语言中Map的底层实现原理,后续需要学习,在此插个眼,学习完之后在这儿总结一下。

Go之Map底层原理学习资源:Go Map底层实现原理

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/182859
推荐阅读
相关标签
  

闽ICP备14008679号