LeetCode热题100的JS解释--哈希

119 阅读5分钟

概括

哈希系列在LeetCode热题中只有3个,在算法题中都是比较容易的题目。

哈希在JS中的表现就是使用Map/Set数据结构,某种程度上,使用Object对象也能达到这个效果,因为object的属性要保持唯一性。 哈希一般用来处理以下类型的题目:

  1. 去重
  2. 分组
  3. 复杂度为O(n)或者O(1)的查找

我们来解一下LeetCode上的题目

题目一

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

难度:简单

输入: nums = [2,7,11,15], target = 9
输出: [0,1]
解释: 因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

输入: nums = [3,2,4], target = 6
输出: [1,2]

输入: nums = [3,3], target = 6
输出: [0,1]

无废话解法

这个题目属于典型的O(1)查找的场景,也是比较简单的一个题目。
假如我们找的是9,遍历到2的时候,只需要看9-2是否存在。遍历到11的时候,只需要判断9-11是否存在。 首先我们想到使用哈希Map,其中数组中的数字本身就是Map的key,而数字对应的下标就是value。

需要注意,在找数字之和的题目中,这个题目之所以简单,是存在以下几个条件:

  1. 数组中无重复数字

  2. 只需要一个结果

  3. 满足taget的数字,只要两个,且同一个数字不能重复使用

后面我们会遇到更复杂的求和题目,这时候使用哈希已经不管用了。

JS 实现

const getSumRes =(nums,target) =>{
    const map = new Map();
    for(let i = 0;i<nums.length;i++){
        const num = nums[i];
        const diff = target - num;
        if(map.has(diff)){
       
            return [map.get(diff),i]
        }else{
        
            map.set(num,i)
        }
    }
}
console.log(getSumRes([2,7,11,15],9))
console.log(getSumRes([3,2,4],6))
console.log(getSumRes([3,3],6))

题目二

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。

难度:简单

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]

输入: strs = [""]
输出: [[""]]

输入: strs = ["a"]
输出: [["a"]]
提示:
  1 <= strs.length <= 104
  0 <= strs[i].length <= 100
  strs[i] 仅包含小写字母

无废话解法

这个题目属于典型的分组场景。 使用Map进行分组,就需要找到唯一key。异位词首先是他们包含的字母是一样的,只是顺序不同,我们只需要将这些字母重新排序,就可以得到唯一key。如:"eat"和"tea"的key,都是 “aet”。
所以,我们的一句话解法就是: 遍历数组,将每个单词的字母排序,排序的结果作为分组的key。

JS 实现

// 先实现排序,将排序的结果作为分组的key

const getKey = (word) => {
    return word.split("").sort().join('')
}

//主逻辑
const groupAnagrams = (strList) => {
    // 实现分组,首先想到的是使用Map。
    // 更简单的方式是使用lodash 的group方法,但是面试没法这么用。
    const res = new Map();
    strList.forEach(item => {
        const key = getKey(item)
        // 当map中不存在key时,就追加一下
        if (res.has(key)) {
            res.get(key).push(item)
            res.set(key, res.get(key))
        } else {
            // 当map中不存在key时,就新增这个key
            res.set(key, [item])
        }
    })
    return res.values()
};

console.log(groupAnagrams(["eat", "tea", "tan", "ate", "nat", "bat"]))

console.log(groupAnagrams(["a"]))

console.log(groupAnagrams([""]))

题目三

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) **的算法解决此问题。

难度:简单

输入: nums = [100,4,200,1,3,2]
输出: 4
解释: 最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

输入: nums = [0,3,7,2,5,8,4,6,0,1]
输出: 9

无废话解法

这是一个复杂度为O(n)的查找。这里要理解这个"连续最长序列",它的含义是‘1234’这种连续的,‘135’就不算。 所以我们最直接的思路就是:

  1. 遍历数组
  2. 每遍历到一个数字n,就看一下n+1是否存在,如果存在则再看n+2是否存在,依次类推 题目要求复杂度是O(n),但是我们第一步就已经O(n)了。因此,第2步就必须使用哈希结构,达到O(1)的复杂度。

JS 实现

const getMaxLength = (nums)=>{
    let maxLength = 0
    // 先将数组转换为Set结果,这样第2步查询的时候,复杂度为O(1)
    let set = new Set(nums)
    
    // 遍历数组
    for(let i = 0;i<nums.length;i++){
        // 判断是否存在n-1,如果有,说明这个数字已经被探过了,就继续遍历下一个 
        //这里可能有点不好理解,需要结合下面的逻辑。
        //假如存在数组[1,3,2],我们遍历到1的时候,已经嗅探过2和3是存在的。所以,当我们遍历到2的时候就只需判断一下2-1是否存在,如果存在,说明2已经被嗅探过了,就不用处理了。这样效率高一点。
       
       if(set.has(nums[i]-1)){
            continue
        }
        let length = 1
        //循环判断n+1,n+2...是否存在
       while(set.has(nums[i]+length)){
            length++
        }
        maxLength = Math.max(maxLength,length)
    }
    return maxLength
}

console.log(getMaxLength([100,4,200,1,3,2]))

console.log(getMaxLength([0,3,7,2,5,8,4,6,0,1]))