diff --git a/top-interview-leetcode150/hash-table/128数组中连续序列的最长长度.js b/top-interview-leetcode150/hash-table/128数组中连续序列的最长长度.js new file mode 100644 index 0000000..d0a5c35 --- /dev/null +++ b/top-interview-leetcode150/hash-table/128数组中连续序列的最长长度.js @@ -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; +} diff --git a/top-interview-leetcode150/hash-table/1两数之和.js b/top-interview-leetcode150/hash-table/1两数之和.js new file mode 100644 index 0000000..1d227b0 --- /dev/null +++ b/top-interview-leetcode150/hash-table/1两数之和.js @@ -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) { + +}; + +/* +创建一个map,map的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); + } +} diff --git a/top-interview-leetcode150/hash-table/202快乐数.js b/top-interview-leetcode150/hash-table/202快乐数.js new file mode 100644 index 0000000..f8c3371 --- /dev/null +++ b/top-interview-leetcode150/hash-table/202快乐数.js @@ -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; +} diff --git a/top-interview-leetcode150/hash-table/205同构字符串.js b/top-interview-leetcode150/hash-table/205同构字符串.js new file mode 100644 index 0000000..f6af9d7 --- /dev/null +++ b/top-interview-leetcode150/hash-table/205同构字符串.js @@ -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和t,s[i]作为hashTable的key,t[i]作为hashTable的值,如果s[i] 在hashTable中存在,检查其对应的value是否和 +t[i]一致,如果不一致,就出现了 a-b, a->c这种一个字符出现多映射的情况,直接返回false,最后遍历一遍hashtable,检查是否存在,多个 +value相同的情况,如果出现则表明出现了,a-c,b->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->c,b->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; +} diff --git a/top-interview-leetcode150/hash-table/219存在重复元素.js b/top-interview-leetcode150/hash-table/219存在重复元素.js new file mode 100644 index 0000000..33671bf --- /dev/null +++ b/top-interview-leetcode150/hash-table/219存在重复元素.js @@ -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-jk时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; +} diff --git a/top-interview-leetcode150/hash-table/242有效的字母异位词.js b/top-interview-leetcode150/hash-table/242有效的字母异位词.js new file mode 100644 index 0000000..7632500 --- /dev/null +++ b/top-interview-leetcode150/hash-table/242有效的字母异位词.js @@ -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中出现的小写字母,值为出现的次数,之后遍历s,s中的每个单词会使其对应的计数减少 +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; +} diff --git a/top-interview-leetcode150/hash-table/290单词规律.js b/top-interview-leetcode150/hash-table/290单词规律.js new file mode 100644 index 0000000..5737f95 --- /dev/null +++ b/top-interview-leetcode150/hash-table/290单词规律.js @@ -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保存已经被映射过的单词,去除最后一次遍历 + */ diff --git a/top-interview-leetcode150/hash-table/383赎金信.js b/top-interview-leetcode150/hash-table/383赎金信.js new file mode 100644 index 0000000..44a9634 --- /dev/null +++ b/top-interview-leetcode150/hash-table/383赎金信.js @@ -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; +} diff --git a/top-interview-leetcode150/hash-table/49字母异位词分组.js b/top-interview-leetcode150/hash-table/49字母异位词分组.js new file mode 100644 index 0000000..2100b24 --- /dev/null +++ b/top-interview-leetcode150/hash-table/49字母异位词分组.js @@ -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作为对象的key,map中不能直接使用,因为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); +}