Day7-哈希表,leetcode454,383,15,18

  1. 四数相加 II
  • 给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n

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

  • 思路:

  • 暴力解法:4个for循环遍历这个数组,要取不同元素相加,如果等于0的话,有个count做一个++的操作,最后返回count

  • 换个思路:先预处理前两个数组,再去处理后两个数组,然后再去集合里查找是否有我们想要的元素,所以考虑使用哈希法。两两数组分开遍历,比如先遍历A、B数组,将两个数组中取出的a+b进行计算,然后计算后的数值放到一个集合里,然后再遍历C、D两个数组,c+d,在遍历C、D数组的时候去判断上面存储的集合中有没有我们想要的元素,四个元素相加等于0,集合选择map结构,key存储a+b,value存储a+b的值出现的次数,遍历C、D时是需要检查target-(c+d),即0-(c+d)有没有出现在集合中,

    1. 用哈希表统计 nums1[i] + nums2[j] 的所有和及出现次数。
    1. 遍历 nums3 和 nums4,查找 -(nums3[i] + nums4[j]) 是否在哈希表中,有则累加出现次- 数。
    1. 返回总数。
var fourSumCount = function (nums1, nums2, nums3, nums4) {
    const twoSumMap = new Map()
    let count = 0
   // 统计nums1和nums2所有和的出现次数
    for (let i = 0; i < nums1.length; i++) {
        for (let j = 0; j < nums2.length; j++) {
            const sum = nums1[i] + nums2[j]
            if (twoSumMap.has(sum)) {
                twoSumMap.set(sum, twoSumMap.get(sum) + 1)
            } else {
                twoSumMap.set(sum, 1)
            }
        }
    }

    // 遍历nums3和nums4,查找-(sum)是否在map中
    for (let i = 0; i < nums3.length; i++) {
        for (let j = 0; j < nums4.length; j++) {
            if (twoSumMap.has(0 - (nums3[i] + nums4[j]))) {
                count+= twoSumMap.get(0 - (nums3[i] + nums4[j])) 
            }
        }
    }
    return count
}


var fourSumCount = function (nums1, nums2, nums3, nums4) {
    const twoSumMap = new Map();
    let count = 0;
    // 统计nums1和nums2数组元素之和,和出现的次数,放到map中
    for(const n1 of nums1) {
        for(const n2 of nums2) {
            const sum = n1 + n2;
            twoSumMap.set(sum, (twoSumMap.get(sum) || 0) + 1)
        }
    }
    // 找到如果0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来
    for(const n3 of nums3) {
        for(const n4 of nums4) {
            const sum = n3 + n4;
            count += (twoSumMap.get(0 - sum) || 0)
        }
    }
    return count;
};


  1. 赎金信
  • 给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。ransomNote 和 magazine 由小写英文字母组成

  • 思路:

  • 因为题目说只有小写字母,那可以采用空间换取时间的哈希策略,用一个长度为26的数组来记录magazine里字母出现的次数。

  • 然后再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。

  • 依然是数组在哈希法中的应用。

// 用数组下标做哈希,统计 magazine 字符出现次数。
// 检查 ransomNote 是否都能被 magazine 提供,不能则提前返回 false。即遍历 ransomNote,遇到一个字母就消耗一个 magazine 里的字母,若不足直接返回 false。
// 全部满足则返回 true。

var canConstruct = function(ransomNote, magazine) {
    // 创建一个长度为 26 的数组 strArr,用来统计每个小写字母出现的次数。
    // base 是字符 'a' 的 ASCII 码(97),用于做下标映射。
    const strArr = new Array(26).fill(0), 
        base = "a".charCodeAt();
    
    // 遍历 magazine,统计每个字母出现的次数,存到 strArr 对应下标。
    for(const s of magazine) {  // 记录 magazine里各个字符出现次数
        strArr[s.charCodeAt() - base]++;
    }

    // 遍历 ransomNote,每遇到一个字符就让对应的计数减一。
    // 如果发现某个字符计数为 0,说明 magazine 里没有足够的字符,直接返回 false。
    for(const s of ransomNote) { // 对应的字符个数做--操作
        const index = s.charCodeAt() - base;
        if(!strArr[index]) return false;  // 如果没记录过直接返回false
        strArr[index]--;
    }
    // 如果所有字符都能满足需求,返回 true。
    return true;
};


  1. 三数之和
  • 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

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

  • 思路:

  • 三数之和的双指针法,一层for循环num[i]为确定值,然后循环内有left和right下标作为双指针,找到nums[i] + nums[left] + nums[right] == 0。

  • 首先将数组排序,然后有一层for循环,从下标0的地方开始,同时定一个下标left定义在i+1的位置上,定义下标right在数组结尾的位置上,

  • a+b+c=0,a=nums[i],b=nums[left],c=nums[right]

  • 若nums[i]+nums[left]+nums[right] > 0,说明此时三数之和大了,因为数组是排序过的,所以right应该向左移动,这样才能让三数之和小一些。

  • 若nums[i]+nums[left]+nums[right]<0,说明三数之和小了,left就向右移动,才能让三数之和大一些,直到left与right相遇为止。

var threeSum = function(nums) {
    const res = [], len = nums.length
    // 将数组排序
    nums.sort((a, b) => a - b)
    for (let i = 0; i < len; i++) {
        let l = i + 1, r = len - 1, iNum = nums[i]
        // 数组排过序,如果第一个数大于0直接返回res
        if (iNum > 0) return res
        // 去重
        // 遍历到下标 i 时,如果当前数字 iNum 和前一个数字 nums[i - 1] 相等,说明这个三元组的第一个数已经用过了,再用会产生重复结果。
        // 所以直接 continue 跳过本次循环,避免重复三元组。
        if (iNum == nums[i - 1]) continue
        while(l < r) {
            let lNum = nums[l], rNum = nums[r], threeSum = iNum + lNum + rNum
            // 三数之和小于0,则左指针向右移动
            if (threeSum < 0) l++ 
            else if (threeSum > 0) r--
            else {
                res.push([iNum, lNum, rNum])
                // 去重
                while(l < r && nums[l] == nums[l + 1]){
                    l++
                }
                while(l < r && nums[r] == nums[r - 1]) {
                    r--
                }
                l++
                r--
            }
        }
    }
    return res
};

// 递归
/**
 *  nsum通用解法,支持2sum,3sum,4sum...等等
 *  时间复杂度分析:
 *  1. n = 2时,时间复杂度O(NlogN),排序所消耗的时间。、
 *  2. n > 2时,时间复杂度为O(N^n-1),即N的n-1次方,至少是2次方,此时可省略排序所消耗的时间。举例:3sum为O(n^2),4sum为O(n^3)
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
    // nsum通用解法核心方法
    function nSumTarget(nums, n, start, target) {
        // 前提:nums要先排序好
        let res = [];
        if (n === 2) {
            res = towSumTarget(nums, start, target);
        } else {
            for (let i = start; i < nums.length; i++) {
                // 递归求(n - 1)sum
                let subRes = nSumTarget(
                    nums,
                    n - 1,
                    i + 1,
                    target - nums[i]
                );
                for (let j = 0; j < subRes.length; j++) {
                    res.push([nums[i], ...subRes[j]]);
                }
                // 跳过相同元素
                while (nums[i] === nums[i + 1]) i++;
            }
        }
        return res;
    }

    function towSumTarget(nums, start, target) {
        // 前提:nums要先排序好
        let res = [];
        let len = nums.length;
        let left = start;
        let right = len - 1;
        while (left < right) {
            let sum = nums[left] + nums[right];
            if (sum < target) {
                while (nums[left] === nums[left + 1]) left++;
                left++;
            } else if (sum > target) {
                while (nums[right] === nums[right - 1]) right--;
                right--;
            } else {
                // 相等
                res.push([nums[left], nums[right]]);
                // 跳过相同元素
                while (nums[left] === nums[left + 1]) left++;
                while (nums[right] === nums[right - 1]) right--;
                left++;
                right--;
            }
        }
        return res;
    }
    nums.sort((a, b) => a - b);
    // n = 3,此时求3sum之和
    return nSumTarget(nums, 3, 0, 0);
};


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

  • 思路:

  • 双指针法

  • 剪枝

  • 去重

  • 四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n2),四数之和的时间复杂度是O(n3) 。

var fourSum = function(nums, target) {
    const len = nums.length;
    if(len < 4) return [];
    nums.sort((a, b) => a - b);
    const res = [];
    for(let i = 0; i < len - 3; i++) {
        // 去重i
        if(i > 0 && nums[i] === nums[i - 1]) continue;
        for(let j = i + 1; j < len - 2; j++) {
            // 去重j
            if(j > i + 1 && nums[j] === nums[j - 1]) continue;
            let l = j + 1, r = len - 1;
            while(l < r) {
                const sum = nums[i] + nums[j] + nums[l] + nums[r];
                if(sum < target) { l++; continue}
                if(sum > target) { r--; continue}
                res.push([nums[i], nums[j], nums[l], nums[r]]);
		
		// 对nums[left]和nums[right]去重
                while(l < r && nums[l] === nums[++l]);
                while(l < r && nums[r] === nums[--r]);
            }
        } 
    }
    return res;
};




参考&感谢各路大神

posted @ 2025-06-02 22:42  安静的嘶吼  阅读(13)  评论(0)    收藏  举报