• JS leetcode 两个数组的交集I II 合集题解分析


    壹 ❀ 引

    前些日子,在与博客园用户MrSmileZhu闲聊中,我问到了他先前在字节跳动面试中遇到了哪些算法题(又戳到了他的伤心处),因为当时面试的高度紧张,原题描述已经无法重现了,但大概与数组合并、求交集相关。比较巧的是我在今年年初有整理过一份数组常用操作的文章的JS 数组常见操作汇总,结果今天leetcode的每日打卡,也正好是求数组交集,此题一共有两题,我分别试了下,也看了其他用户思路,才发现原来花样有这么多,所以这篇文章是关于这两题的思路汇总。不积跬步,无以至千里;不积小流,无以成江海。那么本文开始。

    贰 ❀ 两个数组的交集

    此题来自leetcode349. 两个数组的交集,题目描述如下:

    给定两个数组,编写一个函数来计算它们的交集。

    示例 1:

    输入:nums1 = [1,2,2,1], nums2 = [2,2]
    输出:[2]
    

    示例 2:

    输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
    输出:[9,4]
    

    说明:

    输出结果中的每个元素一定是唯一的。
    我们可以不考虑输出结果的顺序。

    注意,在例子1中,随便两个数组都有两个2,但是认定交集只有一个2;结合说明中输出结果每个元素都是唯一,我们可知题目要求无非是两点。

    1. 这个元素一定是在nums1中与nums2中都有出现
    2. 不管出现几次,只要是同一个元素都认定为是一个元素。

    贰 ❀ 壹 借用哈希表

    所以我的想法是,以其中一个数组为遍历参照物,依次查找另一个数组中有没有当前元素,考虑要求2,所以我还需要一个额外哈希表,记录已经出现过的元素,那么直接上代码:

    /**
     * @param {number[]} nums1
     * @param {number[]} nums2
     * @return {number[]}
     */
    var intersection = function (nums1, nums2) {
        // 定义一个哈希表,记录出现过的元素
        let dic = {},
            ans = [];
        for (let i = 0, len = nums1.length; i < len; i++) {
            // 这个元素在两边数组都有出现,其次它还得是第一次向淮安
            if (nums2.includes(nums1[i]) && !dic[nums1[i]]) {
                // 记录出现过的每个元素
                dic[nums1[i]] = true;
                ans.push(nums1[i]);
            };
        };
        return ans;
    };
    

    思路很简单,只是额外加了一个哈希表用于缓存出现过的元素,比如示例1中的2,不管你出现几次,我只有将你第一次出现时加入进去。

    贰 ❀ 贰 借用set元素独一特性

    其实针对第一个例子,如果我们不用hash表保证元素第一次出现,就会出现元素满足几次就记录几次的问题,比如下面的代码:

    var intersection = function (nums1, nums2) {
        return nums1.filter(item => nums2.includes(item)));
    };
    

    这个实现用于测试示例1,就会得到[2,2]。灵机一动,我们也可以在得到结果之后再加工啊,比如set结构不接受重复元素,所以有了如下实现:

    /**
     * @param {number[]} nums1
     * @param {number[]} nums2
     * @return {number[]}
     */
    var intersection = function (nums1, nums2) {
        return [...new Set(nums1.filter(item => nums2.includes(item)))];
    };
    

    我能想到的也就这两点了,其它的思路均来自leetcode用户秦时明月

    贰 ❀ 叁 借用set结构

    由于我们只是要找两个数组中都有的元素,且只记录第一次,我们完全可以用new Set过滤掉重复元素。

    /**
     * @param {number[]} nums1
     * @param {number[]} nums2
     * @return {number[]}
     */
    var intersection = function (nums1, nums2) {
        // 保证nums1是长度更小那个
        if (nums1.length > nums2.length) {
            [nums1, nums2] = [nums2, nums1];
        };
        let hash = new Set(nums1);
        let res = new Set();
        for (let i = 0; i < nums2.length; i++) {
            // 注意,由于是set结构,这里用has取代了数组的includes
            if (hash.has(nums2[i])) {
                res.add(nums2[i]);
            };
        };
        return [...res];
    };
    

    然而我看到这个思路,是这么想的:

    /**
     * @param {number[]} nums1
     * @param {number[]} nums2
     * @return {number[]}
     */
    var intersection = function (nums1, nums2) {
        if (nums1.length > nums2.length) {
            [nums1, nums2] = [nums2, nums1]
        };
        let hash1 = new Set(nums1);
        let hash2 = new Set(nums2);
        let res = new Set();
        for (let num of hash1) {
            if (hash2.has(num)) {
                res.add(num);
            };
        };
        return [...res];
    };
    

    我在上篇文章中就提出了一个疑问,数组的includes和set的has查找,到底谁更快,在知乎不精确测试中提到,当数据小于一万,has更快,大于一万时,includes更具优势,闲的无聊,我也做了一个测试,但事实证明不管数据如何,has查找似乎都比includes更具优势:

    // 创建一个0-99999的数组
    let arr = Array.from(Array(100000), (v, k) => k);
    let set = new Set(arr);
    // 数组测试,执行100次
    console.time('arr');
    for (let i = 0; i < 100; i++) {
        arr.includes(10000);
    };
    console.timeEnd('arr');
    // set测试,执行100次
    console.time('set');
    for (let i = 0; i < 100; i++) {
        set.has(10000);
    };
    console.timeEnd('set');
    

    大家可以修改数组长度与执行次数,以及需要查找的数,我多次刷新让大家看看时间对比

    由于以上例子数组范围是[0,99999],如果我们将查找的数改为100000,由于不存在这个数,也就是每次查找都会从头找到尾,时间差异就更大了。

    当然还有二分查找等其它思路,这里我不做一一记录,可点击上方秦时明月查看,关于本题先说到这里。

    叁 ❀ 两个数组的交集 II

    来看看题二,题二来源leetcode350. 两个数组的交集 II,题目描述如下:

    给定两个数组,编写一个函数来计算它们的交集。

    示例 1:

    输入:nums1 = [1,2,2,1], nums2 = [2,2]
    输出:[2,2]
    

    示例 2:

    输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
    输出:[4,9]
    

    说明:

    输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
    我们可以不考虑输出结果的顺序。
    进阶:

    如果给定的数组已经排好序呢?你将如何优化你的算法?
    如果 nums1 的大小比 nums2 小很多,哪种方法更优?
    如果 nums2 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

    与题一不同的是,题二要求,如果两个数组同时存在相同元素,即便元素已重复,仍认为是交集,比如实例1中双方都有两个2,所以输出了[2,2]

    叁 ❀ 壹 我的思路,开心爱消除

    按照常规的思路[2,2][2]由于2次对比都满足,会输出[2,2],而本题必须要数量上还对等,所以我将其理解成开心爱消除,找到一个消除一个,直接上代码:

    /**
     * @param {number[]} nums1
     * @param {number[]} nums2
     * @return {number[]}
     */
    var intersect = function (nums1, nums2) {
        let ans = [];
        nums1.forEach((item, index) => {
            let sub = nums2.indexOf(item);
            if (sub > -1) {
                ans.push(item);
                // 找到1个删掉一个
                nums2.splice(sub, 1);
            }
        });
        return ans;
    };
    

    叁 ❀ 其它优秀思路

    我们在题一中利用哈希证明元素是第一次出现,在这里,同理可以借用哈希,记录每个元素出现的次数,满足一次,让次数减一,其实还是同一个道理,这里借用leetcode用户天使爆破组思路:

    /**
     * @param {number[]} nums1
     * @param {number[]} nums2
     * @return {number[]}
     */
    var intersect = function (nums1, nums2) {
        let ans = [];
        let hash = {};
        // 记录每个元素出现次数
        for (let num of nums1) {
            hash[num] ? ++hash[num] : hash[num] = 1;
        };
        // 遍历nums2看看有没有数字在nums1出现过
        for (let num of nums2) { 
            let val = hash[num];
            if (val) {
                ans.push(num); // 推入res数组
                --hash[num]; // 匹配掉一个,就少了一个
            };
        };
        return ans;
    };
    

    题目在进阶中,指出如果两数组已排好序如何优化,假设数组已排序,我们大可使用双指针来解决这个问题,大致图示为:

    直接上代码:

    /**
     * @param {number[]} nums1
     * @param {number[]} nums2
     * @return {number[]}
     */
    var intersect = function (nums1, nums2) {
        // 先排序,才好使用双指针
        nums1.sort((a, b) => a - b);
        nums2.sort((a, b) => a - b); 
        const ans = [];
        let p1 = 0;
        let p2 = 0;
        while (p1 < nums1.length && p2 < nums2.length) {
            if (nums1[p1] > nums2[p2]) {
                p2++;
            } else if (nums1[p1] < nums2[p2]) {
                p1++;
            } else {
                ans.push(nums1[p1]);
                p1++;
                p2++;
            };
        };
        return ans;
    };
    

    那么到这里,本文结束。

  • 相关阅读:
    20210608日报
    数据结构-四则表达式运算
    软工博客归档工具(自用)
    阅读笔记6
    阅读笔记4
    阅读笔记3
    阅读笔记2
    阅读笔记5
    阅读笔记1
    大二下第16周总结
  • 原文地址:https://www.cnblogs.com/echolun/p/13300805.html
Copyright © 2020-2023  润新知