算法训练第6天|454.四数相加II|383. 赎金信|15. 三数之和|18. 四数之和

454.四数相加II

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

题目讲解:代码随想录

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

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

解题思路:最简单的思路是四层for循环,但是这样会超时,时间复杂度为O(n^{4})。

我们可以把4个数组分成2组,在每组中使用map记录2个数组元素的和以及出现的次数,故map的key2个数组中元素的和value元素和出现的次数

在第一组中记录v1+v2。在第二组中记录-v3-v4。使v1+v2 = -v3-v4,即v1+v2+v3+v4 = 0。

// 时间复杂度:O(n^2)
func fourSumCount(nums1 []int, nums2 []int, nums3 []int, nums4 []int) int {
    m := make(map[int]int)
    count := 0

    // 构建nums1和nums2的和的map
    for _, v1 := range nums1{
        for _, v2 := range nums2{
            m[v1+v2]++
        }
    }

    // 遍历nums3和nums4,检查-c-d是否在map中
    for _, v3 := range nums3{
        for _, v4 := range nums4{
            sum := -v3 - v4
            if countVal, ok := m[sum]; ok{
                count += countVal
            }
        }
    }
    return count
}

383. 赎金信

题目链接:383. 赎金信

题目讲解:代码随想录

题目描述:给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。

本题判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成,但是这里需要注意两点。

  • 第一点“为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思”  这里说明杂志里面的字母不可重复使用。

  • 第二点 “你可以假设两个字符串均只含有小写字母。” 说明只有小写字母,这一点很重要。

使用map做:

// 时间复杂度: O(n)
func canConstruct(ransomNote string, magazine string) bool {
    m := make(map[rune]int)

    for _, r := range magazine{
        m[r]++
    }

    for _, r := range ransomNote{
        if _, ok := m[r]; !ok{
            return false
        }else{ // 如果存在
            m[r]--
        }

        if m[r] < 0{
            return false
        }
    }
    return true
}

本题在使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!

 // 时间复杂度: O(n)
func canConstruct(ransomNote string, magazine string) bool {
    record := make([]int, 26)
    for _, v  := range magazine{
        record[v - rune('a')]++
    }

    for _, v := range ransomNote{
        record[v - rune('a')]--
        if record[v - rune('a')] < 0{
            return false
        }
    }
    return true
}

15. 三数之和

题目链接:15. 三数之和

题目讲解:代码随想录

题目描述:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。

 这道题推荐使用双指针来做

  1. 首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。
  2. 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
  3. 如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。

还要注意一些细节:

  • 因为已经是排序过的数组,所以当数组第一个元素大于0时,应直接返回。
  • a的去重
  • b、c的去重

a的去重:

a 如果重复了怎么办,a是nums里遍历的元素,那么应该直接跳过去。

但这里有一个问题,是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同?

如果使用if nums[i] == nums[i + 1]{跳过},这样的话可能会漏掉一些结果。例如当数组为[-1, -1, 2, 3]。当遍历到第一个-1时,同时判断第二个数组元素也为-1,则直接跳过{-1,-1, 2},显然不合理。

如果使用if nums[i] == nums[i-1]{跳过},当遍历到第一个-1时,不会进行操作,后续b和c会继续进行遍历,然后进行a+b+c==0的判断。当遍历到第二个-1时,判断与前一个数组元素值相同,则直接跳过,因为a=-1的情况已经进行判断过,所以第二个a=-1时,则不需要考虑

b、c的去重:(a暂时固定)

例如数组的元素为:[-1,-1,-1,1,1,1,1,1,2,2,2,2],初始时,a为第一个-1,b为第二个-1,c为最后一个2,满足条件[-1, -1, 2]。后续遍历时,当b==-1时,要持续往后移动,当c==2时,要持续往前移动。(a值当前已经固定,b和c再出现重复的值没有意义)

// 时间复杂度:O(n^2)
func threeSum(nums []int) [][]int {
    sort.Ints(nums)
    res := [][]int{}

    for i := 0; i < len(nums) - 2; i++{
        n1 := nums[i]
        if n1 > 0{
            break
        }

        // 去重
        if i > 0 && n1 == nums[i - 1]{
            continue
        }

        l, r := i+1, len(nums) - 1

        for l < r{
            n2, n3 := nums[l], nums[r]
            if n1 + n2 + n3 == 0{
                res = append(res, []int{n1, n2, n3})

                for l < r && nums[l] == n2{
                    l++
                }

                for l < r && nums[r] == n3{
                    r--
                }
            }else if n1 + n2 + n3 < 0{
                l++
            }else{
                r--
            }
        }
    }
    return res
}

18. 四数之和

题目链接:18. 四数之和

题目讲解:代码随想录

题目描述:给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abc 和 d 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

思路其实和三数之和相似,只是多了一层循环。 

但是有一些需要注意的点:不要判断nums[k] > target 就返回了,三数之和 可以通过 nums[i] > 0 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是[-4, -3, -2, -1]target-10,不能因为-4 > -10而跳过。

// 时间复杂度:O(n^3)
func fourSum(nums []int, target int) [][]int {
    if len(nums) < 4{
        return nil
    }

    sort.Ints(nums)
    var res [][]int
    for i := 0; i < len(nums) - 3;i++{
        n1 := nums[i]

        if i > 0 && n1 == nums[i - 1]{
            continue
        }

        for j := i+1; j < len(nums) - 2; j++{
            n2 := nums[j]
            if j > i+1 && n2 == nums[j-1]{
                continue
            }
            l := j + 1
            r := len(nums) - 1
            for l < r{
                n3 := nums[l]
                n4 := nums[r]
                sum := n1 + n2 + n3 + n4
                if sum < target{
                    l++
                }else if sum > target{
                    r--
                }else{
                    res = append(res, []int{n1, n2, n3, n4})
                    for l < r && n3 == nums[l+1]{
                        l++
                    }
                    for l < r && n4 == nums[r-1]{
                        r--
                    }
                    r--
                    l++
                }
            }
        }
    }
    return res
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值