feat: 哈希表:1,49,128,202,205,219,242,290,383

This commit is contained in:
LouisFonda 2025-04-09 22:56:02 +08:00
parent 0d078c09b4
commit 72ac2258de
Signed by: yigencong
GPG Key ID: 29CE877CED00E966
9 changed files with 539 additions and 0 deletions

View File

@ -0,0 +1,58 @@
/**
* https://leetcode.cn/problems/longest-consecutive-sequence/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} nums
* @return {number}
*/
const longestConsecutive = function (nums) {
};
/*
思路题目所给的数组是无序的所以直接对数组排序依次遍历整个数组如果当前元素大于前一个元素并且等于前一个元素加1
把统计的长度加1如果不满足就比较是否比最大长度还大是的话就把这个长度作为为最大长度
*/
function f1(nums) {
nums.sort((a, b) => a - b);
if (nums.length === 0) return 0;
if (nums.length === 1) return 1;
let maxLen = 1; // 最大长度
let curLen = 1; // 统计长度
for (let i = 1; i < nums.length; i++) {
const preNum = nums[i - 1];
if (nums[i] === preNum) {
continue;
} else if (preNum + 1 === nums[i]) {
curLen++;
} else {
maxLen = Math.max(maxLen, curLen);
curLen = 1;
}
}
return Math.max(curLen, maxLen); // 如果后面一个很长的连续序列
}
/*
题目要求我们在O(n)的时间复杂度完成上面的做法使用了一次排序nlogn不符合要求正确的做法可以先用set对整个数组去重
整个过程的时间复杂度为O(n)之后遍历整个Set如果当前的num-1在set中不存在就表明这个num是数组中某个序列的开头开始统计
num+1在set中存在吗如果存在就计数加1如果不存在继续遍历
*/
function f2(nums) {
if (nums.length === 0) return 0;
const set = new Set(nums);
let maxLen = 1;
let curLen = 1;
for (const num of set) {
if (set.has(num - 1)) continue;
// 表明num是某个连续序列的开头直接开始统计
let next = num + 1;
while (set.has(next)) {
curLen++;
next++;
}
maxLen = Math.max(maxLen, curLen);
curLen = 1;
}
return maxLen;
}

View File

@ -0,0 +1,21 @@
/**
*https://leetcode.cn/problems/two-sum/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
const twoSum = function (nums, target) {
};
/*
创建一个mapmap的key为target - num, value为下标遍历nums如果当前num在map中存在就返回[map.get(num), i]i表示当前
下标
*/
function f1(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
if (map.has(nums[i])) return [map.get(nums[i]), i];
map.set(target - nums[i], i);
}
}

View File

@ -0,0 +1,71 @@
/**
*https://leetcode.cn/problems/happy-number/?envType=study-plan-v2&envId=top-interview-150
* @param {number} n
* @return {boolean}
*/
const isHappy = function (n) {
};
/*
根据快乐数的定义我们只需一直计数把计数的结果放到Set中如果在后续的计数中出现了这个结果说明它不是快乐数不能是1,所以要先
检测它是否是1如果是的话就返回
*/
function f1(n) {
// 使用set来记录出现过的数字
const seen = new Set();
// 循环直到满足条件
while (n !== 1) {
// 如果当前数字之前的某次计算出过,说明进入循环
if (seen.has(n)) return false;
// 将当前数字加入set
seen.add(n);
// 计算当前数字的每个位置的平方和
// n = n.toString().split('').reduce((sum, digit) => sum + Number(digit) ** 2, 0);
// 优化:通过取余和除法运算计算每一位的平方和
let sum = 0;
while (n > 0) {
const digit = n % 10;
sum += digit * digit;
n = Math.floor(n / 10);
}
n = sum;
}
return true;
}
/*
利用弗洛伊德环形检测算法
*/
function f2(n) {
let slow = n; // 慢指针
let fast = n; // 快指针
// 快慢指针循环
while (fast !== 1 && getSumOfSquares(fast) !== 1) { // getSumOfSquares(fast) !== 1直接提前判定慢指针
slow = getSumOfSquares(slow);
fast = getSumOfSquares(getSumOfSquares(fast));
if (slow === fast) { // 如果快慢指针相遇,说明出现了循环
return false;
}
}
return true;
}
/**
*
* @param {number} num 要计算各位平方和的数
* @returns 返回计算平方和的数
*/
function getSumOfSquares(num) {
let sum = 0;
while (num > 0) {
const digit = num % 10;
sum += digit * digit;
num = Math.floor(num / 10);
}
return sum;
}

View File

@ -0,0 +1,60 @@
/**
* https://leetcode.cn/problems/isomorphic-strings/?envType=study-plan-v2&envId=top-interview-150
* @param {string} s
* @param {string} t
* @return {boolean}
*/
const isIsomorphic = function (s, t) {
};
/*
s和t的长度是一致的s中的字符会按照某种关系映射到t比如: a->b, b->c,但是不会出现a->c, b->c这种多个字符映射同一个字符如果出现
这种情况直接返回false也不会出现a->b, a->c这种一个字符有多个映射的情况如果有直接返回false
思路 同时遍历s和ts[i]作为hashTable的keyt[i]作为hashTable的值如果s[i] 在hashTable中存在检查其对应的value是否和
t[i]一致如果不一致就出现了 a-b, a->c这种一个字符出现多映射的情况直接返回false最后遍历一遍hashtable检查是否存在多个
value相同的情况如果出现则表明出现了a-cb->c这种多个字符映射到同一个字符的情况
*/
function f1(s, t) {
const map = new Map();
for (let i = 0; i < s.length; i++) {
if (map.has(s[i])) {
if (map.get(s[i]) !== t[i]) return false;
} else {
map.set(s[i], t[i]);
}
}
// 利用set查看map的value是否有重复
const set = new Set();
// eslint-disable-next-line no-unused-vars
for (const [_, value] of map) {
if (set.has(value)) return false;
set.add(value);
}
return true;
}
/*
我们在处理a->b,a->c这种多映射的情况非常好处理一次遍历就行但是在遇到a->cb->c这种被多个字符映射的情况需要遍历
一遍map的所有value这无疑增加了复杂度所以可以再增加一个set用来储存哪些字符应该被映射过了如果出现了一个没有映射
的字符但是它要映射的那个字符在set中已经存在直接返回false英文出现了多个字符映射到同一个字符的情况
*/
function f2(s, t) {
// 创建两个map一个储存字符的映射一个储存哪些字符已经被映射过了
const charMap = new Map();
const hasBeenMap = new Set();
for (let i = 0; i < s.length; i++) {
if (charMap.has(s[i])) {
if (charMap.get(s[i]) !== t[i]) return false;
} else {
// 检查t[i]是否已经被映射
if (hasBeenMap.has(t[i])) return false;
charMap.set(s[i], t[i]); // 将映射关系加入 charMap
hasBeenMap.add(t[i]); // 将 t[i] 标记为已映射
}
}
return true;
}

View File

@ -0,0 +1,67 @@
/**
* https://leetcode.cn/problems/contains-duplicate-ii/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} nums
* @param {number} k
* @return {boolean}
*/
const containsNearbyDuplicate = function (nums, k) {
};
/*
定义两个指针表示abs(i - j) <= k的窗口i向右移动查看当前指向的值在set中是否存在不存在就把这个值存入set如果i-j >k 表明
当前太大了要缩小把j指向的元素从set中剔除掉i++,并将测j指向的值是否在set中存在
*/
function f1(nums, k) {
const set = new Set();
for (let i = 0, j = 0; i < nums.length; i++) {
// 如果 窗口太大,就缩小窗口再检测
if (i - j > k) {
set.delete(nums[j]);
j++;
}
// 检测 nums[i]是否在set中存在
if (set.has(nums[i])) return true;
// 将当前值存入set
set.add(nums[j]);
}
return false;
}
/*
优化上面代码中i-j<k的检测是多余的因为当i>k时i-j一定大于k,因为后续我们会一个值一个值的检测例如当k=3,i=4这个时候
一定要缩小串口直接剔除掉i-k-1表示的元素即可set中剩下的元素还是4个这样可以少用一个下标判断也更快
*/
function f2(nums, k) {
const set = new Set();
for (let i = 0; i < nums.length; i++) {
// 超出滑动窗口
if (i > k) {
set.delete(nums[i - k - 1]);
}
// 检测set中是否含有这个元素
if (set.has(nums[i])) return true;
set.add(nums[i]);
}
return false;
}
/*
直接一次遍历利用map储存之前遍历的所有值和下标如果当前值在map中存在并且当前值的下标减去map中对应值的小标结果小于等于k
则表明发生了重复
*/
function f3(nums, k) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const num = nums[i];
if (map.has(num) && i - map.get(num) <= k) return true;
map.set(num, i);
}
return false;
}

View File

@ -0,0 +1,51 @@
/**
* https://leetcode.cn/problems/valid-anagram/?envType=study-plan-v2&envId=top-interview-150
* @param {string} s
* @param {string} t
* @return {boolean}
*/
const isAnagram = function (s, t) {
};
/*
判断s是否是t的异位词异位词就是重新排列字符顺序的词就是异位词所以异位词的长度和原本单词的长度一致同一个字符出现的次数也一致
题目的输入只包含26个小写字母所利用数组的下标表示t中出现的小写字母值为出现的次数之后遍历ss中的每个单词会使其对应的计数减少
1如果出现某个计数小于0直接返回false
*/
function f1(s, t) {
if (s.length !== t.length) return false;
// 统计t中字母出现的频次
const charCounts = new Array(26).fill(0);
for (let i = 0; i < t.length; i++) {
charCounts[t.charCodeAt(i) - 97]++;
}
for (let i = 0; i < s.length; i++) {
const index = s.charCodeAt(i) - 97;
charCounts[index]--;
if (charCounts[index] < 0) return false;
}
return true;
}
/*
处理unicode字符js中有一个坑使用t[i]取unicode字符时如果这个字符是一个代理对比如emjoy表情就会使用两个unicode16表示
所以应该使用for of来处理应该字符串本身就是一个可迭代对象底层会保证它返回的字符是正确的
*/
function f2(s, t) {
if (s.length !== t.length) return false;
const map = new Map();
for (const ch of t) {
map.set(ch, (map.get(ch) || 0) + 1);
}
for (const ch of s) {
if (!map.has(ch)) return false;
map.set(ch, map.get(ch) - 1);
if (map.get(ch) < 0) return false;
}
return true;
}

View File

@ -0,0 +1,40 @@
/**
*https://leetcode.cn/problems/word-pattern/?envType=study-plan-v2&envId=top-interview-150
* @param {string} pattern
* @param {string} s
* @return {boolean}
*/
const wordPattern = function (pattern, s) {
};
/*
和205类似只不过pattern中的字符映射到s中是一个单词首先处理s把s中每一个单词分离出来用数组储存之后遍历pattern和这个数组如果这个
map中存在这个key就查看当前key对应的值是否和数组的值相等如果不相等就返回false, 如果出现多个字符映射同一个字符也返回false和205一致
这里不过多解释
*/
function f1(pattern, s) {
const map = new Map();
const words = s.split(' ');
if (pattern.length !== s.length) return false;
for (let i = 0; i < pattern.length; i++) {
if (map.has(pattern[i])) {
if (map.get(pattern[i]) !== words[i]) return false;
} else {
map.set(pattern[i], words[i]);
}
}
// 检测是否出现了多个字符映射同一个字符串
const set = new Set();
// eslint-disable-next-line no-unused-vars
for (const [_, value] of map) {
if (set.has(value)) return false;
set.add(value);
}
return true;
}
/*
参考205优化思路一致可以使用数组储存对应映射查找更快使用set保存已经被映射过的单词去除最后一次遍历
*/

View File

@ -0,0 +1,68 @@
/**
* https://leetcode.cn/problems/ransom-note/?envType=study-plan-v2&envId=top-interview-150
* @param {string} ransomNote
* @param {string} magazine
* @return {boolean}
*/
const canConstruct = function (ransomNote, magazine) {
};
/*
思路用hash表统计magazine中的字符出现的数量之后遍历ransomNote如果ransomNote中有字符不在这个hash表中说明magazine不能
构成ransomNote如果这个字符在magazine中存在就使其计数减1如果计数为零就从哈希表中删除这个字符
*/
function f1(ransomNote, magazine) {
const map = new Map(); // 利用hash计数
for (let i = 0; i < magazine.length; i++) {
map.set(magazine[i], (map.get(magazine[i]) || 0) + 1); // 如果map中有这个字符就在原有的基础上加1没有就设置成1
}
// 遍历ransomNote中的字符如果字符在map中不存在就返回false
for (let i = 0; i < ransomNote.length; i++) {
const char = ransomNote[i];
if (map.has(char)) {
map.set(char, map.get(char) - 1);
// 如果发现当前字符计数为零从map中删除这个字符
if (map.get(char) === 0) {
map.delete(char);
}
} else {
return false;
}
}
return true;
}
/*
使用数组来代替hash表因为题目所给的测试用例只有26个小写的英文字符可以使用一个长度为26的数组数组的第一个位置代表a,最后一个位置代表
z,每个位置的值代表这个字符在magazine中出现的位置
思考为什么使用数组会更快虽然hash查找操作时间复杂度理论上式O(1)但是hash计数到对应的链表查找数据还是需要花费时间的在数据量
大的时候可以认为是O(1),但是我们只需统计26个英文字符的数量直接使用数组数组在内存中是连续的通过偏移量查找非常快
*/
function f2(ransomNote, magazine) {
const charsCount = new Array(26).fill(0); // 统计magazine中每一个字符出现的次数
// 统计magazine中的字符
for (let i = 0; i < magazine.length; i++) {
const index = magazine.charCodeAt(i) - 97;
charsCount[index]++;
}
// 遍历ransomNote中的字符在charscount中是否存在
for (let i = 0; i < ransomNote.length; i++) {
const index = ransomNote.charCodeAt(i) - 97;
// if (charsCount[index] > 0) {
// charsCount[index]--;
// } else {
// return false;
// }
if (charsCount[index] === 0) return false;
charsCount[index]--;
}
return true;
}

View File

@ -0,0 +1,103 @@
/**
* https://leetcode.cn/problems/group-anagrams/?envType=study-plan-v2&envId=top-interview-150
* @param {string[]} strs
* @return {string[][]}
*/
const groupAnagrams = function (strs) {
};
/*
题目要求我们对字符串中的异位词分组首先创建一个map遍历strs中的所有字符串查看它是否是map中某一个key的异位词如果是的话
把它插入到这个key对应的数组中去如果不是的话表明他是一个新的字符串所以在map中创建一个映射用来收集它的异位词
*/
function f1(strs) {
const map = new Map();
const result = [];
for (let i = 0; i < strs.length; i++) {
let found = false;
// 查找 map 中是否有对应的异位词
for (const [key, value] of map) {
if (key.length === strs[i].length && isAnagram(strs[i], key)) {
value.push(strs[i]); // 如果是异位词,加入现有的数组
found = true; // 标记找到
break; // 找到就停止遍历
}
}
// 如果没有找到,就创建新的异位词组
if (!found) {
map.set(strs[i], [strs[i]]);
}
}
// 将 map 中的所有异位词数组收集到 result
for (const [, value] of map) {
result.push(value);
}
return result;
}
/*
利用字符序来判断异位词
*/
function f2(strs) {
const map = new Map();
const result = [];
// 遍历所有字符串
for (let i = 0; i < strs.length; i++) {
// 排序字符串,得到标准化形式
const sortedStr = strs[i].split('').sort().join('');
// 如果 map 中已经有这个排序后的字符串,就将当前字符串加入对应的数组
if (map.has(sortedStr)) {
map.get(sortedStr).push(strs[i]);
} else {
// 如果没有,创建新的映射
map.set(sortedStr, [strs[i]]);
}
}
// 将 map 中的所有异位词数组收集到 result
for (const [, value] of map) {
result.push(value);
}
return result;
}
// 检测两个词是否是异位词
function isAnagram(s, t) {
if (s.length !== t.length) return false;
const charCounts = new Array(26).fill(0);
for (let i = 0; i < s.length; i++) {
charCounts[s.charCodeAt(i) - 97]++;
charCounts[t.charCodeAt(i) - 97]--;
}
return charCounts.every((count) => count === 0);
}
/*
利用一个数组来储存str的字符计数如果计数相同就判断它们是异位词这个代码巧妙之处是直接把数组作为key,本质就是调用数组的toString()
之后拿这个string作为对象的keymap中不能直接使用因为map是通过引用来判断的即使内容一样也会判为不同的str如果非要要用map可以
使用 String(count)作为map的key
*/
function f3(strs) {
// eslint-disable-next-line no-new-object
const map = new Object();
for (const s of strs) {
const count = new Array(26).fill(0);
for (const c of s) {
count[c.charCodeAt() - 'a'.charCodeAt()]++;
}
// eslint-disable-next-line no-unused-expressions
map[count] ? map[count].push(s) : map[count] = [s];
}
return Object.values(map);
}