哈希表相关

哈希表

什么是哈希表

首先什么是哈希表,哈希表又称为散列表。直白来讲,数组就是一张哈希表,哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素。

哈希表的作用

一般哈希表都是用来快速判断一个元素是否出现在集合里。时间复杂度为O(1),但是是牺牲了空间换取了时间,因为我们需要额外的数组,set或者map来存放数据,才可以实现快速的查找。

常见的三种哈希结构

  • 数组
  • set 集合
  • map 映射

1. 有效的字母异位词

  1. leetCode242 有效的字母异位词

给定两个字符串 st ,编写一个函数来判断 t 是否是 s 的字母异位词。

思路

  1. 声明计数器,一个对象 const obj = {}
  2. 遍历s字符串,如果遍历到字符串的’a’字母,去看obj[a]是否存在
  3. 不存在说明第一次遍历到’a’字母,那么初始化obj[a] = 1
  4. 如果存在则obj[a] += 1
  5. t字符串同理,它每次减1
  6. 遍历完s字符串后,遍历obj对象,看它的每一对key:value,是否value都是0
/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isAnagram = function(s, t) {
    const sLen = s.length
    const tLen = t.length
    if (sLen !== tLen) {
        return false
    }
    const obj = {}
    for (let i = 0; i < sLen; i++) {
        const currentS = s[i]
        const currentT = t[i]
        obj[currentS] ? obj[currentS]++ : (obj[currentS] = 1)
        obj[currentT] ? obj[currentT]-- : (obj[currentT] = -1)
    }
    return Object.values(obj).every((v) => v === 0)
}

console.log(isAnagram('ana', 'aan'))
/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isAnagram = function(s, t) {
    let map = new Map()
    if (s.length !== t.length) {
        return false
    }
    for (var i = 0; i < s.length; i++) {
        if (!map.has(s[i])) {
            map.set(s[i], 1)
        } else {
            map.set(s[i], map.get(s[i]) + 1)
        }
    }
    for (var j = 0; j < t.length; j++) {
        if (!map.has(t[j]) || map.get(t[j]) === 0) {
            return false
        } else {
            map.set(t[j], map.get(t[j]) - 1)
        }
    }
    return true
}

console.log(isAnagram('ana', 'aan'))
  1. leetCode 383赎金信

给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。

思路

也就是看后面的字母串的字母是否包含前一个字符串的字母,与上题类似

/**
 * @param {string} ransomNote
 * @param {string} magazine
 * @return {boolean}
 */
var canConstruct = function(ransomNote, magazine) {
    let map = {}
    for (let i = 0; i < magazine.length; i++) {
        map[magazine[i]] ? map[magazine[i]]++ : (map[magazine[i]] = 1)
    }

    for (let i = 0; i < ransomNote.length; i++) {
        map[ransomNote[i]] ? map[ransomNote[i]]-- : (map[ransomNote[i]] = -1)
    }
    // console.log(Object.values(map)) //[1,0]
    return Object.values(map).every((d) => d >= 0)
}
console.log(canConstruct('ab', 'aab'))

记录相关数组的迭代方法

  1. forEach

对数组中每个元素运行一次提供的函数
函数接收三个参数:
el 当前循环到的元素
index 当前循环元素的下标
arr 数组本身
后两个参数可选,没有返回值

const arr = [1, 2, 3]
arr.forEach((el, index, arr) => {
        console.log(`${el}, ${index},`, arr)
    })
/*
输出如下
1, 0, [1, 2, 3]
2, 1, [1, 2, 3]
3, 2, [1, 2, 3]
 */
  1. map

该方法将原数组按照一定的规则(即提供的回调函数)映射为一个新数组
返回一个新数组,不改变原数组的值

const arr = [1, 2, 3]
const newArr = arr.map((el) => {
    return el * 2
})
console.log(newArr) //[ 2, 4, 6 ]


const arr = [1, 2, 3]
const newArr = arr.map((el) => {
    // return el * 2
})
//如果没有返回值会返回map
console.log(newArr) //[ undefined, undefined, undefined ]
  1. filter

一般用过滤数组
对数组每一项进行运算,返回运算结果为true的元素
返回一个新数组,不改变原数组的值

const arr = [1, 2, 3, 4, 5]
const newArr = arr.filter((el, index, arr) => {
    return el > 2
})
console.log(newArr) // [ 3, 4, 5 ]
  1. some 返回boolean值,只要其中一个满足条件,就返回true
const arr = [1, 2, 3, 4, 5]
const result = arr.some((el, index, arr) => {
    return el > 2
})
console.log(result) //true
    //全部都不满足就返回false
const result1 = arr.some((el, index, arr) => {
    return el > 5
})
console.log(result1) //false
  1. every,与some相反,只有全部满足条件才会返回true,否则false
const arr = [1, 2, 3, 4, 5]
const result = arr.every((el, index, arr) => {
    return el > 2
})
console.log(result)  //false
  1. reduce
array.reduce(callback,[initialValue])

callback接受4个参数

  • accumulatar 上一次调用回调返回的值,或者是提供的初始值(initialValue)
  • currentValue 数组中正在处理的元素
  • currentIndex 数组中正在处理的元素索引,如果提供了initialValue,从0开始,否则,从1开始
  • array 数组对象本身

例如,使用reduce方法实现累加和

const arr = [1, 2, 3, 4]
const result = arr.reduce((a, b) => {
    return a + b
})
console.log(result) //10

运行过程如下

const arr = [1, 2, 3, 4]
const result = arr.reduce((accumulator, currentValue, currentIndex, array) => {
    // accumulator:上一次回调后的结果
    // currentValue:目前要进行运算的值
    // currentIndex:目前的index
    console.log(accumulator, currentValue, currentIndex)
    return accumulator + currentValue
})
console.log(result)
    /*运行结果
    1 2 1
    3 3 2
    6 4 3
    10
    */

此时我们把初始值传入

const arr = [1, 2, 3, 4]
const result = arr.reduce((accumulator, currentValue, currentIndex, array) => {
    // accumulator:上一次回调后的结果
    // currentValue:目前要进行运算的值
    // currentIndex:目前的index
    console.log(accumulator, currentValue, currentIndex)
    return accumulator + currentValue
}, 10)
console.log(result)
/*
10 1 0
11 2 1
13 3 2
16 4 3
20
*/

reduceRight与reduce类似,不同之处在于它是从最后一个值开始计算的。

const arr = [1, 2, 3, 4]
const result = arr.reduceRight(
    (accumulator, currentValue, currentIndex, array) => {
        // accumulator:上一次回调后的结果
        // currentValue:目前要进行运算的值
        // currentIndex:目前的index
        console.log(accumulator, currentValue, currentIndex)
        return accumulator + currentValue
    },
    10
)
console.log(result)
/*
10 4 3
14 3 2
17 2 1
19 1 0
20
*/
  1. find/findIndex

find方法用于找出第一个符合条件的数组成员,返回该成员

const arr = [1, 2, 3, 4, 5]
const result = arr.find((el, index, arr) => {
    return el > 3
})
console.log(result) // 4

findIndex和这个方法类似,不同之处在于,findIndex是返回第一个符合条件的下标

const arr = [1, 2, 3, 4, 5]
const result = arr.findIndex((el, index, arr) => {
    return el > 3
})
console.log(result) // 3。因为4的下标是3

(咳咳,走远咯走远咯,开始下一题)

  1. leetCode49 字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

思路

也就是说,把含有相同字母的单词分为一组

  1. 定义map存储
  2. 字符串转数组->排序->转字符串为key(将所有异位词排序为同一单词,作为key)
  3. 根据key插入map
  4. map传数组输出
var groupAnagrams = function(strs) {
    let map = new Map()

    strs.forEach((str) => {
        const key = str.split('').sort().join('')
        map.has(key) ? map.get(key).push(str) : map.set(key, [str])
    })
    return Array.from(map.values())
}

console.log(groupAnagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat']))
  1. leetCode 438 找到字符串中所有字母异位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指字母相同,但排列不同的字符串。

思路
  1. 这个方法会超时
/**
 * @param {string} s
 * @param {string} p
 * @return {number[]}
 */
var findAnagrams = function(s, p) {
    let result = []
    let arr = p.split('').sort()

    for (let i = 0; i < s.length - p.length + 1; i++) {
        temp = s
            .substring(i, i + p.length)
            .split('')
            .sort()

        if (temp.toString() == arr.toString()) {
            result.push(i)
        }
    }
    return result
}
console.log(findAnagrams('cbaebabacd', 'abc'))

2. 两个数组的交集

  1. leetCode 349

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

思路

解法一

  1. 同集合对num1去重
  2. 遍历num1,筛选出nums2也包含的值
var intersection = function(nums1, nums2) {
    return [...new Set(nums1)].filter((n) => nums2.includes(n))
}

时间复杂度filter+includes,O(m*n), 空间复杂度O(m)

解法二

  1. 新建一个字典,遍历nums1, 填充字典
  2. 遍历nums2,遇到字典里的值就选出,并从字典中删除
var intersection = function(nums1, nums2) {
    const map = new Map();
    nums1.forEach(n => {
        map.set(n, true)
    })
    const res = []
    nums2.forEach(n => {
        if(map.get(n)){
            res.push(n)
            map.delete(n)
        }
    })
    return res;
};

时间复杂度O(m+n), 空间复杂度:O(m)

解法三 排序加双指针

  1. 首先将数组排序
  2. 定义一个数组保存结果result
  3. 两个指针分别指向两个数组的头部,每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位。如果两个数字相等,且该数字不等于result的尾部元素,同时将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束
  4. 解释下为啥与result的尾部元素比较,因为数组nums1,nums2是经过升序排序的,所以result也必然是升序的,所以新遍历的元素如果不等于result尾部元素,必然大于尾部元素,且不会与result内元素重复(因为是升序的)。
var intersection = function(nums1, nums2) {
    nums1.sort((a, b) => a - b);
    nums2.sort((a, b) => a - b);
    const length1 = nums1.length, length2 = nums2.length;
    let index1 = 0, index2 = 0;
    const result = [];
    while (index1 < length1 && index2 < length2) {
        const num1 = nums1[index1], num2 = nums2[index2];
        if (num1 === num2) {
            // 保证加入元素的唯一性
            if (!result.length || num1 !== result[result.length - 1]) {
                result.push(num1);
            }
            index1++;
            index2++;
        } else if (num1 < num2) {
            index1++;
        } else {
            index2++;
        }
    }
    return result;
};

时间复杂度:O(mlogm+nlogn)
空间复杂度:O(logm+logn),其中 m 和 n 分别是两个数组的长度。空间复杂度主要取决于排序使用的额外空间

3. 快乐数

  1. leetCode 202快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」定义为:

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

输入:19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
思路

1。 如果map中出现了这个数,证明出现了环路,就返回false

/**
 * @param {number} n
 * @return {boolean}
 */
var isHappy = function (n) {
  let map = new Map();
  let t = 0;
  //当map里面有这个元素的时候说明判断进入了循环,永远不可能到1
  while (!map.has(n)) {
    map.set(n, true);
    n = n + '';
    for (let i = 0; i < n.length; i++) {
      t += (n[i] - 0) * (n[i] - 0);
    }
    n = t;
    t = 0;
    if (n === 1) {
      return true;
    }
  }
  //默认结果是false
  return false;
};

4. 两数之和

  1. leetCode 两数之和

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

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

5. 三数之和

  1. leetCode 15 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

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

思路

采用的是双指针

var threeSum = function(nums) {
    const result = []
    let newArr = [...nums].sort((a, b) => a - b)

    for (let i = 0; i < newArr.length - 2; i++) {
        if (i === 0 || newArr[i] !== newArr[i - 1]) {
            let start = i + 1
            let end = newArr.length - 1
            while (start < end) {
                if (newArr[i] + newArr[start] + newArr[end] == 0) {
                    result.push([newArr[i], newArr[start], newArr[end]])
                    start++
                    end--
                    while (start < end && newArr[start] == newArr[start - 1]) {
                        start++
                    }
                    while (start < end && newArr[end] == newArr[end + 1]) {
                        end--
                    }
                } else if (newArr[i] + newArr[start] + newArr[end] < 0) {
                    start++
                } else {
                    end--
                }
            }
        }
    }
    return result
}
let result = threeSum([-1, 0, 1, 2, -1, -4])
console.log(result)

6. 四数之和

  1. leetCode 四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] :0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target

var fourSum = function(nums, target) {
    nums = nums.sort(function(x, y) {
        return x - y
    })
    let result = []
    console.log(nums)
    for (let i = 0; i < nums.length - 3; i++) {
        if (i > 0 && nums[i] === nums[i - 1]) continue
        for (let j = i + 1; j < nums.length - 2; j++) {
            if (j > i + 1 && nums[j] === nums[j - 1]) continue
            let start = j + 1
            let end = nums.length - 1
            while (start < end) {
                if (nums[i] + nums[j] + nums[start] + nums[end] === target) {
                    result.push([nums[i], nums[j], nums[start], nums[end]])
                    start++
                    end--
                    while (start < end && nums[start] === nums[start - 1]) {
                        start++
                    }
                    while (start < end && nums[end] === nums[end + 1]) {
                        end--
                    }
                } else if (nums[i] + nums[j] + nums[start] + nums[end] < target) {
                    start++
                } else {
                    end--
                }
            }
        }
    }
    return result
}

参考

carl代码随想录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值