力扣刷题 - 哈希表

177 阅读6分钟

image.png

哈希表

哈希表都是用来快速判断一个元素是否出现集合里.
常见的三种哈希结构
· 数组
· set (集合)
· map(映射)

快速判断一个元素是否出现集合里的时候,就要考虑哈希法
哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找
数组就是简单的哈希表,但是数组的大小可不是无限开辟的

image.png

242. 有效的字母异位词 简单题

异位词:字符串s和t中每个字符出现的次数都相同
字母异位词:重新排列源单词的字母得到的一个新单词
字符串s和t仅包含小写字母

思路: 暴力求解: 时间复杂度至少得O(n^2)
哈希表: 时间复杂度O(n),空间复杂度为O(1) 以空间换时间
因为串s和t只包含小写字母,那么可以定义一个数组(数组是简单的哈希表),来记录串s中每个字符出现的次数,数组大小为26,初始化为0。 (数组大小是常数级别 ,空间复杂度为O(1))

要想是异位词 首先两个字符串的长度得相等
遍历串s, s[i]-'a'下标所对应的元素+1,然后遍历串t,t[i]-'a'下标所对应的元素-1,如果-1后元素值小于0,说明t中有某个字母出现的次数大于s中出现的次数,不是字母异位词,返回false
如果能遍历完t,则是有效的字母异位词

注意: console.log('b'-'a'); // NaN 
会先将‘b’ 转换成number类型,那么就是NaN, NaN-NaN ==> NaN

var isAnagram = function(s, t) {
   if(s.length != t.lengthreturn false;
   let arr = new Array(26).fill(0);
   // 注意: 不要直接 ‘b’-‘a’ ==> NaN
   let base = 'a'.charCodeAt(); // a --> 97 
   for(let i of s) {
       arr[i.charCodeAt()-base]++;
   }
   for(let i of t){
       arr[i.charCodeAt()-base]--;
       if(arr[i.charCodeAt()-base]<0return false;
   }
   return true;
};

349. 两个数组的交集 简单题

返回两个数组 nums1 和 nums2的交集;输出结果中的每个元素一定是唯一的, 不考虑输出结果的顺序.
image.png ==>理解为: 求两个数组公共的部分,去重.

var intersection = function(nums1, nums2) {
    let set = new Set();
    let nums1Set = new Set(nums1);
    for(let k of nums2) {
        nums1Set.has(k) && set.add(k);
    }
    return Array.from(set);
};

350. 两个数组的交集 II 简单题

image.png

==>理解为: 求两个数组公共的部分,不去重.

var intersect = function(nums1, nums2) {
    let res = new Array();
    let map = new Map();
    for(let k of nums1){
        value = map.get(k) ? map.get(k)+1:1;
        map.set(k,value);
    }
    for(let k of nums2){
      let value = map.get(k)? map.get(k):0;
      if(value > 0) {
          map.set(k,value-1);
          res.push(k);
      }
    }
    return res;
};

先用map记录下第一个数组中的元素放在key,各元素出现的次数放在value. 然后再遍历第二个数组,能在map中找到对应元素,则添加这个元素到结果数组里. value值大于1,HashMap中的value值减1,表示已经找到一个相同的了. 如果value值等于1,则删除该元素.

202. 快乐数 简单题

判断一个数n是不是快乐数.

快乐数定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数

image.png

var getSum = function(n) {
    let s = 0;
    while(n) {
       s += (n%10)**2;
       n = Math.floor(n/10);// 注意一定要取整,不然会超时
    }
    return s;
}
var isHappy = function(n) {
    let set = new Set();   // Set() 里的数是惟一的
    // 如果在循环中某个值重复出现,说明此时陷入死循环,也就说明这个值不是快乐数
    // 循环结束条件 只要能判断出n是否是快乐数就结束循环
    //n == 1 -->是
    //set中有重复数就一定能判断n不是快乐数 结束循环
    while (n !== 1 && !set.has(n)) {
        set.add(n);
        n = getSum(n);
    }
    return n === 1;
};

1. 两数之和 简单题

在数组中找出 和为目标值target的那两个整数,并返回它们的数组下标

image.png

var twoSum = function(nums, target) {
    let map = new Map();
    for(let i = 0; i < nums.length; i++) {
        if(map.has(target-nums[i])) {
            return [i,map.get(target-nums[i])];
        } 
        map.set(nums[i],i);
    }
};

454. 四数相加 II 中等题

四个独立的数组,只要找到A[i]+B[j]+C[K]+D[l] =0就可以,不用考虑有重复的四个元素相加等于0的情况.

var fourSumCount = function(nums1, nums2, nums3, nums4) {
    let map = new Map();
    let count=0;
    for(let i of nums1) {
        for(let j of nums2){
            // let value = map.get(i+j) ? map.get(i+j)+1 : 1;
            // map.set(i+j,value);
             map.set(i+j,(map.get(i+j) || 0)+1);
        }
    }
    for(let i of nums3) {
        for(let j of nums4){
            if(map.has(-(i+j))){
                count = count + map.get(-(i+j));
            }
        }
    }
    return count;
};

383. 赎金信 简单题

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

注意:由小写英文字母组成

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

15. 三数之和 中等题

判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k,同时还满足 nums[i] + nums[j] + nums[k] == 0.

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

image.png

注意[0,0,0,0] 这组数据, 还有 难点在于如何去除重复解

方法一: 哈希解法(不推荐,去重难搞)

两层for循环可以确定a和b的数值了,哈希法来确定0-(a+b)是否在数组里出现过,但题目中说不可以包含重复的三元组,需要去重,而去重比较繁琐,容易超时

方法二: 双指针法:(比哈希高效)O(n^2)

1.将数组升序排序,i从0到nums.length-2,left定义在i+1的位置上,right定义在数组结尾nums.length-1的位置上
2.找nums[i]+nums[left]+nums[right]=0
3.>0,right向左移,让三数之和变小
4.<0,left向右移,让三数之和变大
5.直到left与right相遇

image.png

var threeSum = function(nums) {
   // 不重复的三元组 (a,b,c) ===> 理解为 排序后 a,b,c (a去重,b去重,c去重)
    let len = nums.length;
    // 长度小于3 
    if(len < 3return [];
    // nums数组 排升序
    nums.sort((a,b) => a-b);
    // res 用于存放结果集
    let res = [];
    for(let i = 0; i < len-2; i++){
        if(nums[i] > 0break;
        // a 去重
        // if(nums[i] == nums[i-1]) continue;
        while(nums[i] == nums[i-1]) i++;
        //使用双指针
        let l = i+1,r = len-1;
        while(l < r) {
            let sum = nums[i] + nums[l] + nums[r]; 
            if(sum > 0) {r--;continue;}
            if(sum < 0) {l++;continue;}
            // sum = 0 找到满足条件的 a,b,c, 存入res
            res.push([nums[i],nums[l],nums[r]]);
            // b,c 去重
            while(l < r && nums[l] == nums[++l]);
            while(l < r && nums[r] == nums[--r]);
        }
    }
    return res
};

不重复的三元组(a,b,c) ===> 理解为 排序后 a,b,c (a去重,b去重,c去重)
排序的目的: 让满足条件的三个元素(a,b,c)是排序的
a,b,c去重: 保证(a,b,c)只会出现一次,从而就实现了去重

18. 四数之和 中等题

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

image.png

var fourSum = function(nums, target) {
    let len = nums.length;
    // 长度小于4 
    if(len < 4return [];
    // res 用于存放结果
    let res = [];
    // nums排升序
    nums.sort((a,b) => a-b);
    for(let i = 0; i < len - 3; i++) {
        if(nums[i] > target && target >=0break;// 此处剪枝 可写可不写
        // a 去重
        while(i>0 && nums[i] == nums[i-1])i++;
        for(let j = i+1; j < len - 2; j++) {
            // 此处剪枝 可写可不写
            if(nums[i]+nums[j] > target && target >= 0break; 
            // b 去重
            while(j > i+1 && nums[j] == nums[j-1])j++;
            // 此时已固定 a+b
            // 双指针 滑动
            let l = j+1,r=len-1;
            while(l < r) {
                let sum = nums[i] + nums[j] + nums[l] + nums[r];
                if(sum > target) {r--; continue;}
                if(sum < target) {l++; continue;}
                // 找到满足条件的a,b,c,d
                res.push([nums[i],nums[j],nums[l],nums[r]]);
                // c,d去重
                while(l < r && nums[l] == nums[++l]);
                while(l < r && nums[r] == nums[--r]);
            }
        }
    }
    return res;
};

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

四数之和的双指针解法是两层for循环nums[k]+nums[i]为确定值,依然是循环内有left和right下标作为双指针,找到满足条件的四个数.
三数之和的时间复杂度是o(n^2),四数之和的时间复杂度是o(n^3). 同理,五数之和、六数之和等等都采用这种解法.

对于三数之和 双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法.
四数之和 双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法.