什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
一、基础知识
详细参考
二、常见的三种哈希结构
在JavaScript中,我们也可以使用哈希结构来存储和查找数据。与C++不同,JavaScript没有直接提供红黑树实现的set
和map
,但它提供了Set
和Map
这两种内置数据结构,它们的底层实现基于哈希表。
一、JavaScript中的哈希结构
JavaScript中常用的哈希结构主要包括:
-
数组(Array)
-
Set(集合)
-
Map(映射)
-
对象(Object,虽然不是严格意义上的哈希表,但也经常用于键值映射)
数组在JavaScript中是最常见的数据结构,它适用于存储有序的数据集合。与哈希结构不同,数组的查询和修改通常需要遍历,时间复杂度可能达到O(n)
。
数组的创建方式
-
使用字面量(最常见的方式):
const arr = [1, 2, 3, 4];
-
使用
new Array()
(可以指定长度):const arr = new Array(5); // 创建一个长度为5的空数组
-
使用
Array.of()
(创建包含具体值的数组):const arr = Array.of(1, 2, 3, 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
的键可以是任何类型。相比Map
,Object
的查询效率通常较低,特别是在存储大量数据时。
结构 | 底层实现 | 是否有序 | 是否允许重复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来存放数据,才能实现快速的查找。
如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法!
三、有效的字母异位词
已解答
简单
相关标签
相关企业
给定两个字符串
s
和t
,编写一个函数来判断t
是否是s
的。
示例 1:
输入: s = "anagram", t = "nagaram" 输出: true示例 2:
输入: s = "rat", t = "car" 输出: false提示:
1 <= s.length, t.length <= 5 * 104
s
和t
仅包含小写字母进阶: 如果输入字符串包含 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.快乐数
编写一个算法来判断一个数
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;
};
已解答
简单
相关标签
相关企业
给你两个字符串:
ransomNote
和magazine
,判断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
ransomNote
和magazine
由小写英文字母组成
/**
* @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;
};