代码随想录算法训练营Day 7|454. 四数相加 II、383. 赎金信、15. 三数之和、18. 四数之和

38 阅读5分钟

454. 四数相加 II

[题目链接] (leetcode.cn/problems/4s…)

暴力方法 时间复杂度 O(n^4)   空间复杂度 O(1)

哈希方法 时间复杂度 O(n^2)   空间复杂度 O(n^2)

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

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

思路

本题适用于map哈希表,首先遍历A、B两个数组,将所有可能的和 出现次数 存放在map中,再遍历C、D两个数组,查找map中是否存在0—(C+D),存在value个,count+value

/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @param {number[]} nums3
 * @param {number[]} nums4
 * @return {number}
 */
var fourSumCount = function(nums1, nums2, nums3, nums4) {
    //先遍历A、B两个数组,将所有可能的和 出现次数 存放在map中
    //for of 遍历key  for in 遍历value
    let map = new Map()
    let count = 0
    for(let n1 of nums1){
        for(let n2 of nums2){
            map.set(n1+n2, map.get(n1+n2)+1 || 1)
        }
    }

    //再遍历C、D两个数组时,查找map中是否存在0—(C+D),存在几个,count+几
    for(let n3 of nums3){
        for(let n4 of nums4){
            if(map.has(0-(n3+n4))){
                count += map.get(0-(n3+n4))
            }
        }
    }
    return count
};

383. 赎金信

题目链接

要求:给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

思路

本题与242. 有效的字母异位词类似,提议为:看字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a,且字符串a中字符只能使用一次,不能重复。

/**
 * @param {string} ransomNote
 * @param {string} magazine
 * @return {boolean}
 */
var canConstruct = function(ransomNote, magazine) {
    let map = new Array(26).fill(0)
    let base = 'a'.charCodeAt()
    for(let s of magazine){
        map[s.charCodeAt()-base]++
    }
    for(let s of ransomNote){
        if(!map[s.charCodeAt()-base]) return false
        map[s.charCodeAt()-base]--
    }
    return true
};

15. 三数之和

题目链接

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

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

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

思路

1.双指针法:首先将数组nums排序,便于去重;然后一层for循环i由0开始遍历,然后再定义left=i+1right=nums.length-1开始从头尾往中间遍历,类似于二分查找方法,若nums[i]+nums[left]+nums[right]>0,则说明三数之和大了,left应该往右移动,若三叔之和<0,则right应该向左移动。

本题难点在于去重,需要考虑三个数的去重,即nums[i]nums[left]nums[right]:
(1)nums[i]重复,由于i在for循环中,所以直接跳过这个循环就行,但究竟是判断 nums[i]nums[i + 1]是否相同,还是判断 nums[i]nums[i-1] 是否相同?如果判断nums[i] == nums[i+1] ,那么就会把[-1, -1, 2]这种情况跳过,所以要判断nums[i] == nums[i-1]
(2)而nums[left]nums[right]的去重则需要在三数之和=0的时候,在去重完成后需要left++right--,开始下一次循环。

时间复杂度  O(n^2)   空间复杂度  O(1)

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    nums.sort((a,b)=>(a-b))
    let res= []
    for(let i=0; i<nums.length; i++){
        if(nums[i]>0){
            return res
        }
        //对a去重
        if(i>0 && nums[i]==nums[i-1])continue
        let left=i+1, right=nums.length-1
        while(left<right){
            if(nums[i]+nums[left]+nums[right] > 0){
                right--
            }else if(nums[i]+nums[left]+nums[right] < 0){
                left++
            }else{
                res.push([nums[i], nums[left], nums[right]])
                //对b、c去重
                while(left<right && nums[left] == nums[left+1]) left++
                while(left<right && nums[right] == nums[right-1]) right-- 
                left++
                right--
            }
        }
    }
    return res
};

2.哈希解法:两层for循环就可以确定 a 和 b 的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。
把符合条件的三元组放进vector中,然后再去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。

时间复杂度  O(n^2)   空间复杂度  O(1)

var threeSum = function(nums) {
    let res = []
    nums.sort()
    for(let i=0; i<nums.length; i++){
       if(nums[i]>0) break
       if(i>0 && nums[i] == nums[i-1]){
           continue
       } 
       let set = new Set()
       for(let j=i+1;j<nums.length;j++){
           //b去重
           if(j>i+2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2]){
               continue
           }
           let c= 0- (nums[i]+nums[j])
           if(set.has(c)){
               res.push([nums[i], nums[j], c])
               set.delete(c) //c去重
           }else{
               set.add(nums[j])
           }
       }
    }
       return res

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

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

思路

本题整体思路跟15. 三数之和类似,但是在剪枝的时候有一些细节需要注意,不要判断nums[k] > target 就返回了,三数之和 可以通过 nums[i] > 0 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。逻辑需要变成nums[i] > target && (nums[i] >=0 || target >= 0)

时间复杂度  O(n^3)   空间复杂度  O(1)

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[][]}
 */
var fourSum = function(nums, target) {
    let res = []
    nums.sort((a,b)=> (a-b))
    for(let i=0; i<nums.length; i++){
        //剪枝
        if(nums[i]>target && nums[i]>=0 ) break
        if(i>0 && nums[i] == nums[i-1]) continue
        for(let j=i+1; j<nums.length; j++){
            //剪枝
            if(nums[i]+nums[j] > target && nums[i] +nums[j] >= 0) break
            if(j>i+1 && nums[j] == nums[j-1]) continue
            let left = j+1, right = nums.length-1
            while(left<right){
                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.push([nums[i], nums[j], nums[left], nums[right]])
                    while(left<right && nums[left] == nums[left+1]) left++
                    while(left<right && nums[right] == nums[right-1]) right--
                    left++
                    right--
                }
            }
        }
    }
    return res
};