哈希表 - 基础+字母异位词(数组) - JS

什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。

一、基础知识

详细参考

代码随想录
 

二、常见的三种哈希结构

在JavaScript中,我们也可以使用哈希结构来存储和查找数据。与C++不同,JavaScript没有直接提供红黑树实现的setmap,但它提供了SetMap这两种内置数据结构,它们的底层实现基于哈希表。

一、JavaScript中的哈希结构

JavaScript中常用的哈希结构主要包括:

  1. 数组(Array)

  2. Set(集合)

  3. Map(映射)

  4. 对象(Object,虽然不是严格意义上的哈希表,但也经常用于键值映射)

数组在JavaScript中是最常见的数据结构,它适用于存储有序的数据集合。与哈希结构不同,数组的查询和修改通常需要遍历,时间复杂度可能达到O(n)

数组的创建方式
  1. 使用字面量(最常见的方式):

    const arr = [1, 2, 3, 4];
  2. 使用 new Array()(可以指定长度):

    const arr = new Array(5); // 创建一个长度为5的空数组
  3. 使用 Array.of()(创建包含具体值的数组):

    const arr = Array.of(1, 2, 3, 4);
  4. 使用 Array.from()(从可迭代对象创建数组):

    const arr = Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']
结构是否有序是否允许重复访问方式查询效率增删效率
Array有序索引O(n)O(n)
使用示例
const arr = [1, 2, 3, 4];
console.log(arr.includes(3)); // true
arr.push(5); // 添加元素
arr.splice(1, 1); // 删除元素
console.log(arr); // [1, 3, 4, 5]

数组在JavaScript中是最常见的数据结构,它适用于存储有序的数据集合。与哈希结构不同,数组的查询和修改通常需要遍历,时间复杂度可能达到O(n)

结构是否有序是否允许重复访问方式查询效率增删效率
Array有序索引O(n)O(n)
使用示例
const arr = [1, 2, 3, 4];
console.log(arr.includes(3)); // true
arr.push(5); // 添加元素
arr.splice(1, 1); // 删除元素
console.log(arr); // [1, 3, 4, 5]

三、Set 结构

JavaScript的Set类似于C++的unordered_set,底层实现是哈希表,因此其查询和增删操作的时间复杂度一般为O(1)

结构底层实现是否有序是否允许重复访问方式查询效率增删效率
Set哈希表无序迭代器O(1)O(1)
使用示例
const set = new Set();
set.add(1);
set.add(2);
set.add(2); // 不会重复添加
console.log(set.has(2)); // true
set.delete(1);
console.log(set.size); // 1

四、Map 结构

JavaScript的Map类似于C++的unordered_map,其键值存储方式基于哈希表,因此查询和增删操作的时间复杂度为O(1)

结构底层实现是否有序是否允许重复Key访问方式查询效率增删效率
Map哈希表无序迭代器O(1)O(1)
使用示例
const map = new Map();
map.set('name', 'Alice');
map.set('age', 25);
console.log(map.get('name')); // 'Alice'
console.log(map.has('age')); // true
map.delete('age');
console.log(map.size); // 1

五、对象(Object)

在JavaScript中,对象(Object)也可以用来存储键值对,但它的键必须是字符串或符号,而Map的键可以是任何类型。相比MapObject的查询效率通常较低,特别是在存储大量数据时。

结构底层实现是否有序是否允许重复Key访问方式查询效率增删效率
Object哈希表无序.[]O(1)O(1)
使用示例
const obj = { name: 'Alice', age: 25 };
console.log(obj.name); // 'Alice'
delete obj.age;
console.log('age' in obj); // false

六、总结

  • Array:适用于存储有序数据,查询和增删效率较低。

  • Set:类似于C++的unordered_set,无序,不允许重复。

  • Map:类似于C++的unordered_map,无序,键值对存储,键可以是任意类型。

  • Object:键值对存储,键必须是字符串或符号,查询效率不如Map

  • JavaScript没有类似C++ std::set 这种基于红黑树的有序集合,但可以使用Array.sort()来实现排序。

在实际开发中,如果需要高效的键值映射,优先使用Map而不是Object;如果需要存储唯一值的集合,使用Set;如果数据有序且需要索引访问,则使用Array

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法

但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。

如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!

三、有效的字母异位词

242. 有效的字母异位词

已解答

简单

相关标签

相关企业

给定两个字符串 st ,编写一个函数来判断 t 是否是 s

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

输入: s = "rat", t = "car"
输出: false

提示:

  • 1 <= s.length, t.length <= 5 * 104
  • st 仅包含小写字母

进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

var isAnagram = function(s, t) {
    if (s.length !== t.length) return false; // 长度不同,直接返回 false

    let hashs = new Array(26).fill(0); // 初始化数组大小为26,填充0
    // let hashs=[]
    for(let i=0;i<s.length;i++){
        hashs[s.charCodeAt(i) - 'a'.charCodeAt(0)]++;
    }
    for(let i=0;i<t.length;i++){
        hashs[t.charCodeAt(i) - 'a'.charCodeAt(0)]--;
    }
    for(let i=0;i<hashs.length;i++){
        if(hashs[i]!==0) {return false}
    }
    return true
};

优化:1.for (let i = 0; i < s.length; i++) { hashs[s.charCodeAt(i) - 'a'.charCodeAt(0)]++; // 统计s字符频率 hashs[t.charCodeAt(i) - 'a'.charCodeAt(0)]--; // 统计t字符频率 }

索引计算方式是相同的。这样可以减少一次遍历,提高效率。

2.every() 是 JavaScript 数组的一个内置方法,用于检查数组中的 所有元素 是否都满足给定的条件。

语法:array.every(callback(element, index, array))

它会遍历数组中的每个元素,并将其传递给回调函数,如果所有元素都返回 true,则最终 every() 也返回 true,否则返回 false

count回调函数的参数,它代表 hashs 数组中的每个元素不需要提前定义,而是由 every() 方法自动传入的。

var isAnagram = function(s, t) {
    if (s.length !== t.length) return false; // 长度不同,直接返回 false

    let hashs = new Array(26).fill(0); // 初始化数组大小为26,填充0

    for (let i = 0; i < s.length; i++) {
        hashs[s.charCodeAt(i) - 'a'.charCodeAt(0)]++; // 统计s字符频率
        hashs[t.charCodeAt(i) - 'a'.charCodeAt(0)]--; // 统计t字符频率
    }

    return hashs.every(count => count === 0); // 检查所有频率是否归零
};

注意:JS中不可以这样字符之间直接相减,也就是说hashs[s[i]-'a']这么写是错误的。JS中需要通过charCodeAt() 方法来获取字符的 ASCII 码,然后才能计算。

四、202.快乐数

202. 快乐数

 

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

「快乐数」 定义为:

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

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

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

示例 2:

输入:n = 2
输出:false

提示:

  • 1 <= n <= 231 - 1

循环操作,对数字拆分求平方和。出现1就结束返回true,出现重复出现过的数字就返回false 。

可以用一个数组存储所有元素,来判断是否出现过。

难点是算平方和有三种方式:

方法1:数学运算(不转字符串)
function sumOfDigitSquares(n) {
  let sum = 0;
  n = Math.abs(n); // 处理负数
  
  while (n > 0) {
    const digit = n % 10;      // 获取最后一位数字
    sum += digit * digit;      // 平方后累加
    n = Math.floor(n / 10);    // 去掉最后一位
  }
  
  return sum;
}

// 示例
console.log(sumOfDigitSquares(123));  // 14 (1² + 2² + 3² = 1 + 4 + 9)
console.log(sumOfDigitSquares(-45)); // 41 (4² + 5² = 16 + 25)
方法2:字符串转换(更直观)
function sumOfDigitSquares(n) {
  return String(Math.abs(n))         // 转为字符串并处理负数
    .split('')                       // 拆分为字符数组
    .map(Number)                     // 转数字
    .reduce((sum, digit) => sum + digit * digit, 0); // 平方后求和
}

// 示例
console.log(sumOfDigitSquares(123));  // 14
方法3:一行代码版
const sumOfDigitSquares = n => [...String(Math.abs(n))].reduce((s, d) => s + (+d)**2, 0);

算法实现

/**
 * @param {number} n
 * @return {boolean}
 */

// function sumOfDigitSquares(n) {
//   return String(Math.abs(n))         // 转为字符串并处理负数
//     .split('')                       // 拆分为字符数组
//     .map(Number)                     // 转数字
//     .reduce((sum, digit) => sum + digit * digit, 0); // 平方后求和
// }
function sumOfDigitSquares(n) {
    let sum=0;
    while(n){
        let a=n%10
        n=Math.floor(n/10);
        sum+=a*a;
    }
    return sum;
}
var isHappy = function(n) {
    let arr=new Array();
    while(sumOfDigitSquares(n)!==1){
        if(arr.includes(sumOfDigitSquares(n))) {
        return false;    
        }else{
            arr.push(sumOfDigitSquares(n));
            n=sumOfDigitSquares(n);
  }
    }
    return true;
};

383. 赎金信

已解答

简单

相关标签

相关企业

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

输入:ransomNote = "aa", magazine = "ab"
输出:false

示例 3:

输入:ransomNote = "aa", magazine = "aab"
输出:true

提示:

  • 1 <= ransomNote.length, magazine.length <= 105
  • ransomNotemagazine 由小写英文字母组成
/**
 * @param {string} ransomNote
 * @param {string} magazine
 * @return {boolean}
 */
var canConstruct = function(ransomNote, magazine) {
    const record=new Array(26).fill(0);

    for(let i=0;i<magazine.length;i++){
        record[magazine.charCodeAt(i)-'a'.charCodeAt(0)]++
    }

    for(let i=0;i<ransomNote.length;i++){
        if(!record[ransomNote.charCodeAt(i)-'a'.charCodeAt(0)]){
        return false    
        }
        
        record[ransomNote.charCodeAt(i)-'a'.charCodeAt(0)]--
    }
    return true;

};

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值