代码随想录算法训练营第七天|哈希表part02

78 阅读1分钟

454.四数相加II

题目链接:454. 四数相加 II - 力扣(LeetCode)

第一想法

用map

试过的四个数就放在集合之中,并且是key:value的形式

这样可以避免重复,也可以根据值找到索引

但是怎么从这四个数组里面选出四个数,暂时没什么头绪

思路

还是类似于1.两数之和这一题的思路

构造一个集合,循环遍历查找集合中满足条件的元素

步骤如下:

  • 遍历a,b数组,将其中的两个数相加
  • 创建一个map,key为两数相加和,value为和出现的次数
  • 遍历c,d数组,从集合中找key为(0-c-d)的值,如果有,则返回出现次数
  • 如果没有,说明这四个数组中不存在满足条件的四个值

JS代码如下:

var fourSumCount = function(nums1, nums2, nums3, nums4) {
    let map = new Map();
    let num = 0;
    // 把数组1和2的和存储到map中
    for(let i = 0; i < nums1.length; i++){
        for(let j = 0; j < nums2.length; j++){
            let sum = nums1[i] + nums2[j];
            let value = 1;
            if(map.has(sum)){
                value += map.get(sum);
            }
            map.set(sum,value)
        }
    }
    // 在数组3和4中遍历,寻找满足条件的情况
    for(let i = 0; i < nums3.length; i++){
        for(let j = 0; j < nums4.length; j++){
            let sum = nums3[i] + nums4[j];
            if(map.has(0-sum)){
                num += map.get(0-sum);
            }
​
        }
    }
    return num;
​
};

总结

这题的大体思路和两数之和很像

不过需要想想怎么构造出一个集合,再从集合中找满足条件的值

一开始以为需要返回的是四个下标,结果题目问的实际上是符合条件的数量

这种情况的话,哈希表的方法非常适合了

另外,需要注意一些细节

比如第一次提交时,满足条件时我写的是num += 1

这就会出现报错了

实际上程序可以更精简一点,if判断可以被替代:

for(const n1 of nums1) {
        for(const n2 of nums2) {
            const sum = n1 + n2;
            twoSumMap.set(sum, (twoSumMap.get(sum) || 0) + 1)
        }
    }

383. 赎金信

题目链接:383. 赎金信 - 力扣(LeetCode)

第一想法

和242一样的,构造一个存储字母出现次数的哈希表

思路

构建一个哈希表,存储第二个单词出现的字母的次数

遍历第一个单词,把每个字母对应于哈希表上的位置的值减1

如果第一个字母可以由第二个字母组成

那么遍历结束后,数组中不可能有负数

所以,可以用数组中是否有负数来判断是否符合条件

JS代码如下:

var canConstruct = function(ransomNote, magazine) {
    let arr = new Array(26).fill(0);
    let mode = 'a'.charCodeAt();
    for(let i of magazine){
        let n = i.charCodeAt() - mode;
        arr[n]++;
    }
    for(let i of ransomNote){
        let n = i.charCodeAt() - mode;
        arr[n]--;
    }
    for(let i = 0; i < 26; i++){
        if(arr[i] < 0){
            return false;
        }
    }
    return true;
    
};

总结

我这里用了三次循环遍历,其实可以简单一些,在第二次循环遍历时就做逻辑判断

for(const s of ransomNote) { // 对应的字符个数做--操作
        const index = s.charCodeAt() - base;
        if(!strArr[index]) return false;  // 如果没记录过直接返回false
        strArr[index]--;
    }
    return true;

如果第一个单词中有出现哈希表中没有记录(值为0)的字母,则直接返回false

另外,这里用map看上去会更方便一些

但是,在c++情况下:

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

JS中,一般情况下,数组比map更节约空间,因为不需要存储键值对。

如果要快速查找或者更新元素,map会更高效

另外,因为JS中会自动分配和释放内存,所以不需要太关注内存管理

15. 三数之和

题目链接:15. 三数之和 - 力扣(LeetCode)

第一想法

遍历一遍统计所有a+b的情况

map的key可以是对象,让key为一个二元数组,存储元素的index

再遍历一遍数组,找到满足a+b+c ===0 的元素,将元素index与map中的key对比,是否重复

如果没有重复,感觉元素index返回符合条件的三个数

这样做,复杂度有点太高了

而且使用map也比较复杂

思路

双指针法:

  • 将数组升序排列
  • i指向第一个元素,并且使用一个for循环
  • left指向i的下一个元素,right指向最后一个元素
  • 根据目前三数之后的情况移动指针

tips:

题目说了必须是不重复的三元数组,那什么时候会有重复的情况呢?

image-20230321111712108

JS代码如下:

var threeSum = function(nums) {
    nums.sort((a,b) => a - b);
    let result = [];
​
    for(let i = 0; i < nums.length; i++){
        if(nums[i] > 0){
            return result;
        }
        //对i去重
        if(i > 0 && nums[i] == nums[i-1]){
            continue;
        }
        let left = i + 1;
        let 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{
                result.push([nums[i],nums[left],nums[right]]);
                // 左右去重
                while(left < right && nums[right - 1] == nums[right]){
                    right--;
                }
                while(left < right && nums[left + 1] == nums[left]){
                    left++;
                }
                 left++;
                 right--;
            }
           
        }
    }
    return result;
};

总结

找三个数等于

对一个数进行循环,另外两个数用双指针

其实把数组转化为有序的,这题就和数组的双指针的题目非常像了

i和左右指针的去重很重要

也需要注意,写题目的过程中因为去重边界的处理,出了两次bug

while(left < right && nums[right - 1] == nums[right]){
                    right--;
                }

这里应该用while,而不是if

18. 四数之和

题目链接:18. 四数之和 - 力扣(LeetCode)

第一想法

和454有一点像,不过454返回的是满足条件的数量

这一题需要返回满足条件的元组,而且互不重复

创建map构建哈希表的话,有点太复杂了

这一题应该还是双指针解法

比15多套一层for循环

思路

两层循环

注意每层循环的break条件以及去重处理

JS代码如下:

var fourSum = function(nums, target) {
    nums.sort((a,b) => a-b)
    let res = [];
    let len = nums.length;
    for(let i = 0; i < len-1; i++){
        if(nums[i] > target && target > 0){
            break;
        }
        // 去重处理
        if(nums[i] == nums[i-1] && i > 0){
            continue;
        }
        for(let j = i+1; j < len; j++){
            if(nums[i] + nums[j] > target && target > 0){
            break;
        }
            // 去重处理
            if(nums[j] == nums[j-1] && j > i+1){
                continue;
            }
            let l = j + 1;
            let r = len - 1;
            while(l < r){
                if(nums[i]+nums[j]+nums[l]+nums[r] > target){
                    r--;
                }else if(nums[i]+nums[j]+nums[l]+nums[r] < target){
                    l++;
                }else{
                    res.push([nums[i],nums[j],nums[l],nums[r]]);
                    // 去重处理
                    while(nums[r-1] == nums[r]){
                        r--;
                    }
                    while(nums[l+1] == nums[l]){
                        l++;
                }
                l++;
                r--;
            }
            }
            
            
        }
    }
    return res;
};

总结

这一题思路和上一题是一样的

但是细节有很多,必须注意

比如,

if(nums[j] == nums[j-1] && j > i+1){
                continue;
                }

这里j > i+1这个一定不能漏了!

res直接在最后一起返回,防止混乱。