赞
踩
题目链接:454. 四数相加 II
这个题目和两数之和的做法非常相似,将前两个变量和后两个变量合并,就和两数之和非常相似。此外,由于本题是在四个数组中做四数相加,因此不需要考虑重复的情况。所以,这里直接考虑使用哈希表来解决这个问题,将A+B的和存入map中,再判断target-(C+D)的值在map中是否存在相应的key,如果存在,需要加上该key的数量(value)
- func fourSumCount(nums1 []int, nums2 []int, nums3 []int, nums4 []int) int {
-
- map1 := make(map[int]int)
- total := 0
- for i := 0; i < len(nums1); i++ {
- for j := 0; j < len(nums2); j++ {
- sum := nums1[i] + nums2[j]
- map1[sum]++
- }
- }
-
- for i := 0; i < len(nums3); i++ {
- for j := 0; j < len(nums4); j++ {
- if _, ok := map1[0-(nums3[i]+nums4[j])]; ok {
- total += map1[0-(nums3[i]+nums4[j])]
- }
- }
- }
-
- return total
- }
题目链接:383. 赎金信
这个题目基本没有什么难度,就是判断构成字符串A的字符集合是否是构成字符串B的字符集合的子集。乍一看,直接把字符串B的字符和对应数量当作键值对存在map中,再使用循环遍历字符串A的所有字符,对其进行两个判断:1)当前字符是否存在map中;2)map中当前字符的数量是否足够。
使用hash能够很好的解决该问题,但是我们不能无脑使用哈希表,毕竟哈希表需要耗费大量空间,我们可以针对具体的问题使用数组进行模拟,因为数组具有一个和哈希表一样的特点,就是存取数据所耗费的时间复杂度都是O(1)。因此,根据对题观察,我们发现英文字符的数量是存在上限的,所有直接使用数组模拟map也能很好的解决问题。
- func canConstruct(ransomNote string, magazine string) bool {
-
- map1 := make(map[string]int)
-
- magazine_s := []rune(magazine)
- ransomNote_s := []rune(ransomNote)
- for _, n := range magazine_s {
- map1[string(n)]++
- }
-
- for _, m := range ransomNote_s {
- if _, ok := map1[string(m)]; !ok {
- return false
- } else {
- if map1[string(m)] > 0 {
- map1[string(m)]--
- } else {
- return false
- }
- }
- }
-
- return true
- }
下面的使用数组的解法代码来源:代码随想录
- func canConstruct(ransomNote string, magazine string) bool {
- record := make([]int, 26)
- for _, v := range magazine { // 通过record数据记录 magazine里各个字符出现次数
- record[v-'a']++
- }
- for _, v := range ransomNote { // 遍历ransomNote,在record里对应的字符个数做--操作
- record[v-'a']--
- if record[v-'a'] < 0 { // 如果小于零说明ransomNote里出现的字符,magazine没有
- return false
- }
- }
- return true
- }
题目链接:15. 三数之和
这道题由于惯性思想,直接套用前面四数相加II的想法,无脑套用map来解决,后来发现使用map得出的解会出现很多的重复解,而且这个去重的方法没有想出来,查看代码随想录的代码也没有看懂,所以直接学习代码随想录给出的双指针解法。
核心思想可以分为以下几个步骤:
对于A+B+C=0
1、对数组进行排序,这样方便后续利用数组的元素的相对位置信息
2、第一层循环nums[i]用来固定A的位置,去寻找后面B和C的位置
3、利用left和right指针,分别指向A的下一个位置和数组的尾部位置
4、第二层循环,限定right > left,当找到一个符合条件的三元组时,两个指针同时移动,否则根据和大于0还是小于0来移动right和left
上面四个步骤只是大概方法,还有几个细节需要注意:
1、去重
对于A的去重,放在第一层循环里,判断当前位置的元素是否等于上一个元素
- // 对第一个元素进行去重,不能使用nums[i] == nums[i+1],否则会错过[-1,-1,2]这组数据
- if i > 0 && nums[i] == nums[i-1] {
- continue
- }
对于B和C的去重,则是需要在找到了相应的解之后,再进行去重,否则会遗漏一部分解,例如[0,0,0,0]
- res = append(res, []int{nums[i], nums[left], nums[right]})
- //停在重复元素的最后一个
- for right > left && nums[left] == nums[left+1] {
- left++
- }
- // 停在重复元素的最前一个
- for right > left && nums[right] == nums[right-1] {
- right--
- }
2、剪枝
通过剪枝操作来减少搜索的时间。根据本题,由于数组经过排序,如果固定位置的A处的值大于0那么就没有必要进行搜索了,直接返回即可。这个剪枝操作需要根据题目内容来设计,但是这个思想能够很好的帮主我们减少程序不必要的运行时间。
- func threeSum(nums []int) [][]int {
- sort.Ints(nums)
- res := [][]int{}
-
- if nums[0] > 0 {
- return res
- }
-
- for i := 0; i < len(nums); i++ {
- if nums[i] > 0 {
- return res
- }
- // 对第一个元素进行去重,不能使用nums[i] == nums[i+1],否则会错过[-1,-1,2]这组数据
- if i > 0 && nums[i] == nums[i-1] {
- continue
- }
- left := i + 1
- right := len(nums) - 1
-
- for right > left {
- if nums[i]+nums[left]+nums[right] > 0 {
- right--
- } else if nums[i]+nums[left]+nums[right] < 0 {
- left++
- } else {
- res = append(res, []int{nums[i], nums[left], nums[right]})
- //停在重复元素的最后一个
- for right > left && nums[left] == nums[left+1] {
- left++
- }
- // 停在重复元素的最前一个
- for right > left && nums[right] == nums[right-1] {
- right--
- }
- right--
- left++
- }
- }
-
- }
- return res
-
- }
题目来源:18. 四数之和
这个题目和前面三数之和的做法是一样的,但是一些细节方面的处理略微有些不同。
初始思路:和三数之和一样,找固定位置,由于这是四个数,需要利用双指针寻找的话,就得固定前两个位置也就是A和B,C和D的搜索同样使用left和right指针的模式,然后对A进行剪枝和去重操作。
上面思路写完代码运行之后,发现还有重复的解。后续查看卡哥的思路才发现,我们对A和B都做了固定位置的操作,但是只是对A做了去重操作,并没有对B进行去重操作,这样会导致在B位置出现重复值。例如[2,2,2,2,2],target=8. 如果不对B去重,那么会有两个解,分别对应两个位置【0,1,2,4】和【0,2,3,4】。
因此这个地方对B也需要进行去重操作,以此类推,五数之和、六数之和都是如此
去重
- if j > i+1 && nums[j] == nums[j-1] {
- continue
- }
剪枝
对于B的剪枝原理上和A相同,和三数之和又有所不同,三数之和题目里taget是固定的,等于0,而本题中target是动态变化的,因此不能简单的通过一个当前固定值大于0就停止搜索.
1、对于A的剪枝,需要考虑nums[A]大于target的同时,还需要判断nums[A]>0 || target > 0,防止出现target=-10,nums[A]=-4,直接跳过搜索。
2、对于B的剪枝,则需要在A的基础上,考虑一下nums[A]+nums[B]>0即可
- func fourSum(nums []int, target int) [][]int {
- sort.Ints(nums)
- res := [][]int{}
- for i := 0; i < len(nums); i++ {
- //减枝,提高速度,避免不必要的判断
- // 前面nums[i] > 0使用用于过滤target<0的情况
- if nums[i] > target && (nums[i] > 0 || target > 0) {
- return res
- }
- if i > 0 && nums[i] == nums[i-1] {
- continue
- }
- for j := i + 1; j < len(nums); j++ {
- if nums[i]+nums[j] > target && (nums[j] > 0 || target > 0) {
- break
- }
- if j > i+1 && nums[j] == nums[j-1] {
- continue
- }
-
- left := j + 1
- right := len(nums) - 1
- for right > left {
- //这里不能先去重,要等当前符合条件的四元组加入res之后再去判断重复,否则会漏掉四元组
-
- if nums[i]+nums[j]+nums[left]+nums[right] > target {
- right--
- } else if nums[i]+nums[j]+nums[left]+nums[right] < target {
- left++
- } else {
-
- res = append(res, []int{nums[i], nums[j], nums[left], nums[right]})
- for right > left && nums[left] == nums[left+1] {
- left++
- }
- for right > left && nums[right] == nums[right-1] {
- right--
- }
- left++
- right--
-
- }
- }
-
- }
- }
- return res
-
- }
关于哈希表的实现方式,在Go语言当中,官方的库里只有数组和map的实现方式,并没有set,只有第三方库的set实现,这是我后续需要去学习的地方。
其次,需要在做题时需要分析何时使用数组,何时使用map。要依据两者数据结构的特点来选择相应的实现方式
最后,对于Go语言中Map的底层实现原理,后续需要学习,在此插个眼,学习完之后在这儿总结一下。
Go之Map底层原理学习资源:Go Map底层实现原理
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。