哈希表

哈希表

1. 两数之和

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

输入: nums = [2,7,11,15], target = 9
输出:[0,1]
解释: 因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

解题思路:

  • 使用 hash 表来存放 key-元素值,value-元素下标

  • 遍历数组,如果当前元素与 hash 表中的值恰好之和为 target,那么就成功了,否则将当前元素加入到 hash 表中。

func twoSum(nums []int, target int) []int {
    m := make(map[int]int)
    for i, num := range nums {
        if idx, exists := m[target-num]; exists {
            return []int{i, idx}
        } else {
            m[num] = i
        }
    }
    return []int{}
}

15. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

输入: nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

解题思路:

  • 排序,将数组排序成有序的

  • 固定一个数,然后使用双指针遍历另外两个元素。

  • 去重可以使用 hashmap,也可以使用比较当前元素与前一个元素值的方式。

注意点:

  • 三数之和不要使用 hash 表的方法!!!hash 表的方法会很繁琐,记住就行,三数之和要使用 排序+双指针 的方法!!

  • 得到一个结果之后两个指针需要分别向左,向右移动。这样可以防止出现死循环。

  • 得到的数组最后必须要进行去重,为了防止出现重复的数组。

func threeSum(nums []int) [][]int {
    res := make([][]int, 0)
    history := make(map[string]struct{}) // 去重的 hashmap
    // 排序
    sort.Slice(nums, func(i, j int) bool {
        return nums[i] < nums[j]
    })
    for i := 0; i <= len(nums)-3; i++ {
        left := i + 1
        right := len(nums) - 1
        for left < right {
            if nums[i]+nums[left]+nums[right] < 0 {
                left++
            } else if nums[i]+nums[left]+nums[right] > 0 {
                right--
            } else {
                subNum := []int{nums[i], nums[left], nums[right]}
                /** --------- bug 记录 ----------
                没有去重,导致出现重复的三元组. 现在这里使用 hashmap 来进行去重
                */
                s := encode(subNum)
                if _, exists := history[s]; !exists {
                    history[s] = struct{}{}
                    res = append(res, subNum)
                }
                /** --------- bug 记录 -------------
                刚开始 left++,right-- 没写,导致程序在这里死循环!
                */
                left++
                right--
            }
        }
    }
    return res
}

func encode(nums []int) string {
    return fmt.Sprintf("%v", nums)
}

以下是去重版本2,没有使用 hashmap 进行去重,而是通过判断上一个元素是否与这一个元素相等来进行的去重。

在写这道题的过程中,出现了三处 bug:

  • 没有去重代码

  • 使用去重方法2的时候,应该开始新循环的地方没有写 continue,导致代码继续执行

  • 得到结果的时候,left -- 和 right ++ 没有写,导致程序在这处死循环。

// 去重版本2
func threeSum_(nums []int) [][]int {
    res := make([][]int, 0)
    // 排序
    sort.Slice(nums, func(i, j int) bool {
        return nums[i] < nums[j]
    })
    for i := 0; i <= len(nums)-3; i++ {
        // 第一个元素也需要被去重
        if i > 0 && nums[i] == nums[i-1] {
            continue
        }
        left := i + 1
        right := len(nums) - 1
        for left < right {
            /** --------- bug 记录 ----------
            没有去重,导致出现重复的三元组. 因为重复的原因在于遍历数组的时候,相邻元素可能相等,那么去重就可以跳过相邻的元素。
            */
            // 左面, 右面相邻元素重复,需要跳过以达到去重的目的, 比如left > i + 1的目的是让left不是第一个元素,必须从第二个开始去重,rigth < len(nums)-1 同理
            if left > i+1 && nums[left] == nums[left-1] {
                left++
                continue
                /** ---- bug 记录 -----
                刚开始没有写 continue,导致去重之后继续执行后序代码,与预期不相符合,所以说想要开始新循环的时候一定要写 continue,要养成习惯!!
                */
            }
            if right < len(nums)-1 && nums[right] == nums[right+1] {
                right--
                continue
            }
            if nums[i]+nums[left]+nums[right] < 0 {
                left++
                continue
            } else if nums[i]+nums[left]+nums[right] > 0 {
                right--
                continue
            } else {
                subNum := []int{nums[i], nums[left], nums[right]}
                res = append(res, subNum)
                /** --------- bug 记录 -------------
                刚开始 left++,right-- 没写,导致程序在这里死循环!
                */
                left++
                right--
            }
        }
    }
    return res
}

454. 四数相加 II

给你四个整数数组 nums1nums2nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

输入: nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出: 2
解释:
两个元组如下:

  1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

解题思路:

本题乍一看好像与三数之和相同,只不过由三个数变成了四个数,仔细一看发现不一样,三数之和是在一个数组中,所以使用固定一个数,移动另外两个数的方式。而这道题是在四个数组中,所以使用 hashmap 的方法,先计算两项和出现的次数,然后将两项和合并,计算总共出现的次数。

func fourSumCount(nums1 []int, nums2 []int, nums3 []int, nums4 []int) int {
    // 先计算两项和, 再计算另外两项和, 然后再计算它俩的和
    m1 := make(map[int]int) //两项和出现的次数
    for i := 0; i < len(nums1); i++ {
        for j := 0; j < len(nums2); j++ {
            m1[nums1[i]+nums2[j]]++
        }
    }
    m2 := make(map[int]int)
    for i := 0; i < len(nums3); i++ {
        for j := 0; j < len(nums4); j++ {
            m2[nums3[i]+nums4[j]]++
        }
    }
    count := 0
    for sum1, count1 := range m1 {
        for sum2, count2 := range m2 {
            if sum2+sum1 == 0 {
                count += count2 * count1
            }
        }
    }
    return count
}
posted @ 2022-12-23 22:05  kohn  阅读(21)  评论(0)    收藏  举报