代码随想录算法训练营第七天| 454.四数相加II 、 383. 赎金信 、 15. 三数之和 、 18. 四数之和 「哈希表」

120 阅读4分钟

454. 四数相加 II

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

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

思路
想不出来

代码
写不出来

优化:

看了代码随想录的思路,脑袋一拍,是的如果四个数没办法用1对1的差值去处理,那直接先变成两个数就好了。
思路: 整两个map集合,分别放前两个数组和后两个数组的所有和的情况(作为键),值为出现的次数,然后对第一个map遍历,在第二个map中找第一个map中的键,如果存在,就把两个值相乘然后累加到count里,返回count
看了一下大佬的写法,发现自己写的太麻烦,大佬是在创建第一个map的时候放键值对进去,然后遍历后两个数组的时候,直接把所有和用来在第一个map中查找,然后把结果累加进count中返回。

代码

// 我的写法
/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @param {number[]} nums3
 * @param {number[]} nums4
 * @return {number}
 */
var fourSumCount = function(nums1, nums2, nums3, nums4) {
    const getSumMap = (nums1,nums2) => {
        const oneMap = new Map()
        for (let i of nums1) {
            for (let j of nums2) {
                if(oneMap.has(i+j)) {
                    oneMap.set(i+j,oneMap.get(i+j)+1)
                }else {
                    oneMap.set(i+j,1)
                }
            }
        }
        return oneMap
    }
    const front = getSumMap(nums1,nums2)
    const behind = getSumMap(nums3,nums4)
    console.log(front)
    console.log(behind)
    let count = 0
    function handleMapElements(value, key, map) {
        let diff = 0 - key
        if (behind.has(diff)) {
            count += behind.get(diff)*front.get(key)
        }
    }
    front.forEach(handleMapElements)
    return count
};
// 大佬写法(自己写了一遍)
/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @param {number[]} nums3
 * @param {number[]} nums4
 * @return {number}
 */
var fourSumCount = function(nums1, nums2, nums3, nums4) {
    const oneMap = new Map()
    for (let i of nums1) {
        for (let j of nums2) {
            const sum = i+j
            oneMap.set(sum,(oneMap.get(sum)||0)+1)
        }
    }
    let count = 0 
    for (let i of nums3){
        for (let j of nums4) {
            const sum = i+j
            count += (oneMap.get(0-sum)||0)
        }
    }
        
    return count
};

总结:

难度陡增,希望自己的逻辑思考能力能在刷题过程中成长。

383. 赎金信

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

思路
一开始没想出来,回头看了一下242.有效的字母异位做出来了。其实整体和242非常像,先声明一个数组,长度为26,代表26个英文字母,然后对ransomNote进行遍历,把出现的字母作为数组的索引下标,出现次数作为值填入到数组中,然后对magazine进行遍历,把出现的字母作为数组的索引下标,让对应下标的值减去出现的次数,最后得到一个数组,当然这其中的索引都是处理过的,字母的unicode减去'a'的unicode。 然后对数组结果进行遍历,因为ransomNote的字符是由magazine组成的,所以magazine中出现的字符如果ransomNote不存在也算正确,那就把判断条件从!==0改为>0。

代码

/**
 * @param {string} ransomNote
 * @param {string} magazine
 * @return {boolean}
 */
var canConstruct = function(ransomNote, magazine) {
    const table = new Array(26).fill(0)
    for (let i =0; i<ransomNote.length;i++) {
        let index = ransomNote.charCodeAt(i)-97
        table[index] = (table[index]||0)+1
    }
    for (let i = 0;i<magazine.length;i++) {
        table[magazine.charCodeAt(i)-97]--
    }
    console.log(table)
    for (let k of table) {
        if (k>0) {
            return false
        }
    }
    return true
};

优化:

看了一下大佬的写法,是先对magazine进行遍历,得到一个可能出现的字符的数组,然后再对ransomNote进行遍历,判断这个值是否在数组中出现过,没有就直接返回false,通过一个逻辑实现了magazine中不存在ransomNote中出现的字符,和多次出现的字符两个功能。

代码

/**
 * @param {string} ransomNote
 * @param {string} magazine
 * @return {boolean}
 */
var canConstruct = function(ransomNote, magazine) {
    const strArr = new Array(26).fill(0), 
        base = "a".charCodeAt();
    for(const s of magazine) {  // 记录 magazine里各个字符出现次数
        strArr[s.charCodeAt() - base]++;
    }
    for(const s of ransomNote) { // 对应的字符个数做--操作
        const index = s.charCodeAt() - base;
        if(!strArr[index]) return false;  // 如果没记录过直接返回false
        strArr[index]--;
    }
    return true;
};

总结:

15. 三数之和

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

思路
没有思路。

代码
写不出来。

优化:

看完代码随想录的思路,其实双指针很好理解,但是对于去重那一块有点懵,然后看了卡哥的视频,看了一遍就基本理解了,虽然很多人可能不理解,比如 [-1,-1,2] 这样的数组,用 nums[i] === nums[i-1] 这样的判断方式为什么在 i===1 的时候是能起到去重效果的。我的理解是,因为这是一个三个数的数组,当i为0的时候,nums[i]是-1,把另一个-1作为第二个固定的数,他们的和要等于0,另一个额外的数必须是2,如果i为1的时候,nums[i]为-1,把2作为第二个固定的数,他们的和要等于0,另一个额外的数必须是-1,这样就重复了。
注意:我在自己写代码的时候还会疏漏一个点,就是在对左右指针进行移动的时候,left也存在nums[left] === nums[left-1]的情况,那么也要进行去重,right同理。

代码

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    nums.sort((a,b)=>a-b)
    const res = []
    for (let i = 0;i<nums.length-2;i++) {
        if (nums[i]>0) continue
        if (i>0&&nums[i] === nums[i-1]) continue
        let left = i+1
        let right = nums.length-1
        while (left < right) {
            let sum = nums[i] + nums[left] + nums[right] 
            if (sum> 0) {
                right--
            }else if (sum < 0) {
                left++
            }else {
                res.push([nums[i],nums[left],nums[right]])
                left++
                right--
                while (nums[left] === nums[left-1]) {
                    left++
                }
                while (nums[right] === nums[right+1]) {
                    right--
                }
            }
        }
    }
    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 你可以按 任意顺序 返回答案 。

思路
利用三数之和的双指针法,多设置一个循环嵌套,但是不知道为什么在处理的时候总是返回不了正确答案。

代码
没写完。

优化:

看了代码随想录的思路,和我想的是一样的,但是有两个点:

  1. 在剪枝操作上,不能直接判断数组第一个元素大于目标值返回,考虑到负数情况,所以对剪枝要附加一些额外条件。
  2. 对第二层循环进行去重时,二层循环的索引值应该大于一层循环索引+1,也就是二层循环的初始值。

代码

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[][]}
 */
var fourSum = function(nums, target) {
    nums.sort((a,b)=>a-b)
    const res = []
    for (let i = 0;i<nums.length-3;i++) {
        if (target>0&&nums[i]>target) break
        if (i>0&&nums[i]===nums[i-1]) continue
        for (let j = i+1;j<nums.length-2;j++) {
            if (target>0&&nums[i]+nums[j]>target) break
            if (j>i+1&&nums[j]===nums[j-1]) continue
            let left = j+1
            let right = nums.length-1
            while (left < right) {
                const sum = nums[i]+nums[j]+nums[left]+nums[right]
                if (sum > target) {
                    right--
                }else if ( sum < target) {
                    left++
                }else {
                    res.push([nums[i],nums[j],nums[left],nums[right]])
                    left++
                    right--
                    while (nums[left] === nums[left-1]) {
                        left++
                    }
                    while (nums[right] === nums[right+1]) {
                        right--
                    }
                }
            }
        }
    }
    return res
};

总结:

自己做这道题的时候明确的想到了,要利用双指针法,但是在剪枝和去重的细节操作上思考还是不到位。

Day7总结

今天的哈希表题目,感觉比昨天难一些,而且后两题用了哈希思想的双指针解法,对思维能力有一定要求。