1. 两数之和 leetcode.cn/problems/tw…
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返回答案。
var twoSum = (nums, target) => {
// 创建一个对象来存储出现过的数字和对应的索引
const prevNums = {};
for (let i = 0; i < nums.length; i++) { // 遍历元素
const curNum = nums[i]; // 当前元素
const targetNum = target - curNum; // 满足要求的目标元素
const targetNumIndex = prevNums[targetNum]; // 在prevNums中获取目标元素的索引
if (targetNumIndex !== undefined) { // 如果存在,直接返回 [目标元素的索引, 当前索引]
return [targetNumIndex, i];
} else { // 如果不存在,说明之前没出现过目标元素
prevNums[curNum] = i; // 存入当前的元素和对应的索引
}
}
};
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const targetNum = target - nums[i];
if (map.has(targetNum)) {
return [map.get(targetNum), i];
}
map.set(nums[i], i); // 等同于 else {map.set(nums[i], i);}
}
// 如果没有找到,返回空数组
return [];
}
// 测试函数
console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]
console.log(twoSum([3, 2, 4], 6)); // [1, 2]
console.log(twoSum([3, 3], 6)); // [0, 1]
const 声明的特性: 使用 const 声明的变量在声明时必须初始化,并且在其作用域内不能重新赋值。 每次迭代创建一个新的块作用域,const 变量在每个新的作用域内都是新的,因此可以在每次迭代中被重新声明和赋值。
块作用域: for 循环中的每次迭代都会创建一个新的块作用域。在每个块作用域内,const 声明的变量都是新的变量,与前一次迭代中的变量无关。
避免意外修改: 使用 const 可以确保 complement 在每次迭代中不会被意外重新赋值,从而增强代码的安全性和可读性。
20. 有效的括号 leetcode.cn/problems/va…
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括号。
栈是一种后进先出的数据结构,非常适合用于处理括号匹配问题。具体思路是:
- 遇到左括号时,将其压入栈中。
- 遇到右括号时,检查栈顶元素是否为对应的左括号。如果是,则将栈顶元素弹出;否则,字符串无效。
- 在遍历完字符串后,如果栈为空,则字符串有效;否则,字符串无效。
// 方法1:栈 + Map
var isValid = function(s) {
const stack = [];
const map = new Map([
['(', ')'],
['{', '}'],
['[', ']']
]);
for (const char of s) {
if (map.has(char)) {
// 如果是左括号,将其对应的右括号压入栈中
stack.push(map.get(char));
} else {
// 如果是右括号,检查栈顶元素是否匹配
if (stack.pop() !== char) {
return false;
}
}
}
// 如果栈为空,表示所有括号都匹配
return stack.length === 0;
}
// 方法2:栈 + 对象哈希
function isValid(s) {
const stack = [];
const map = {
'(': ')',
'{': '}',
'[': ']'
};
for (const char of s) {
if (map[char]) {
// 如果是左括号,将其对应的右括号压入栈中
stack.push(map[char]);
} else {
// 如果是右括号,检查栈顶元素是否匹配
if (stack.pop() !== char) {
return false;
}
}
}
// 如果栈为空,表示所有括号都匹配
return stack.length === 0;
}
// 测试函数
console.log(isValid("()")); // true
console.log(isValid("()[]{}")); // true
console.log(isValid("(]")); // false
console.log(isValid("([)]")); // false
console.log(isValid("{[]}")); // true
169. 多数元素 leetcode.cn/problems/ma…
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。你可以假设数组是非空的,并且给定的数组总是存在多数元素。
方法1: Boyer-Moore 投票算法
- 候选人选择
1)初始化一个计数器 count 为 0 和一个候选人 candidate 为 null。
2)遍历数组 nums 中的每个元素 num:
如果 count 为 0,将 candidate 设置为当前元素 num。
如果当前元素 num 等于 candidate,将 count 增加 1;否则,将 count 减少 1。
3)遍历结束后,candidate 就是多数元素。 - 候选人验证
在选择候选人之后,我们需要验证这个候选人是否确实是多数元素。
在实际应用中,这一步通常是隐含的,因为题目保证了多数元素的存在。
但如果没有这个保证,可通过再次遍历数组来确认候选人的出现次数。
通过一个具体例子来详细解释这个算法的执行过程。
假设输入数组是 [2, 2, 1, 1, 1, 2, 2],目标是找到多数元素。
初始化:count = 0, candidate = null
遍历数组:
第一个元素是 2,因为 count = 0,所以设置 candidate = 2,count = 1
第二个元素是 2,因为 candidate = 2,所以 count = 2
第三个元素是 1,因为 candidate ≠ 1,所以 count = 1
第四个元素是 1,因为 candidate ≠ 1,所以 count = 0
第五个元素是 1,因为 count = 0,所以设置 candidate = 1,count = 1
第六个元素是 2,因为 candidate ≠ 2,所以 count = 0
第七个元素是 2,因为 count = 0,所以设置 candidate = 2,count = 1
最后的候选人是 2。
function majorityElement(nums) {
let count = 0;
let candidate = null;
for (const num of nums) {
if (count === 0) {
candidate = num;
}
count += (num === candidate) ? 1 : -1;
}
return candidate;
}
function majorityElement(nums) {
let count = 0;
let candidate = null;
// 第一次遍历:选择候选人
for (let num of nums) {
if (count === 0) {
candidate = num;
}
count += (num === candidate) ? 1 : -1;
}
// 第二次遍历:验证候选人(如果题目没有保证多数元素存在的话)
count = 0;
for (let num of nums) {
if (num === candidate) {
count++;
}
}
if (count > Math.floor(nums.length / 2)) {
return candidate;
} else {
throw new Error("No majority element found");
}
}
方法2:哈希Map
var majorityElement = function(nums) {
const map = new Map();
for (const num of nums) {
if (map.has(num)) {
map.set(num, map.get(num) + 1);
} else {
map.set(num, 1);
}
// map.set(num, (map.get(num) || 0) + 1);
// 在处理非布尔值时,|| 返回第一个真值或最后一个假值。map.get(ch) || 0 是一个逻辑或运算符表达式。
// 它的意思是,如果 map.get(ch) 的值为 undefined(表示字符 ch 还没有出现过),则使用 0 作为默认值;否则,使用 map.get(ch) 的值。
}
for (const [key, value] of map) {
if (value > nums.length / 2) { // 在逻辑上,更严谨的做法是使用 Math.floor(nums.length / 2),以确保得到的是整数部分。
return key;
}
}
// return null; // 这是一个保险措施,理论上不会走到这里,因为题目假设数组总是存在多数元素
// 如果没有找到,抛出错误
throw new Error("No majority element found");
};
方法3:排序
如果一个元素是多数元素,那么它在排序后的数组中必然位于数组的中间位置。
var majorityElement = function(nums) {
nums.sort((a, b) => a - b);
// 返回 nums 数组中间位置的元素,使用 Math.floor() 函数向下取整确保得到的是中间位置的整数索引
return nums[Math.floor(nums.length / 2)]; // Math.floor() 函数总是返回小于等于一个给定数字的最大整数。
};
// 测试函数
console.log(majorityElement([3, 2, 3])); // 3
console.log(majorityElement([2, 2, 1, 1, 1, 2, 2])); // 2
console.log(majorityElement([1])); // 1
448. 找到所有数组中消失的数字 leetcode.cn/problems/fi…
给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。
方法1:哈希Map
var findDisappearedNumbers = function(nums) {
const map = new Map();
nums.forEach(num => map.set(num, true)); // 使用Map记录每个数字的存在
const res = [];
for (let i = 1; i <= nums.length; i++) {
if (!map.has(i)) {
res.push(i); // 如果数字i不在Map中,加入结果数组
}
}
return res;
}
方法2:哈希Set
var findDisappearedNumbers = function(nums) {
const set = new Set(nums);
const res = [];
for (let i = 1; i <= nums.length; i++) {
if (!set.has(i)) {
res.push(i);
}
}
return res;
};
方法3:数组哈希
function findDisappearedNumbers(nums) {
const n = nums.length;
const present = new Array(n).fill(false);
// 标记出现的数字
for (const num of nums) {
present[num - 1] = true;
}
// 找到所有没有出现的数字
const result = [];
for (let i = 0; i < n; i++) {
if (!present[i]) {
result.push(i + 1);
}
}
return result;
}
// 测试函数
console.log(findDisappearedNumbers([4, 3, 2, 7, 8, 2, 3, 1])); // [5, 6]
console.log(findDisappearedNumbers([1, 1])); // [2]
console.log(findDisappearedNumbers([1, 2, 2, 4])); // [3]
- 存在重复元素 leetcode.cn/problems/co…
给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
方法1:哈希Map
var containsDuplicate = function(nums) {
const map = new Map();
for (const num of nums) {
if (map.has(num)) {
return true;
} else {
map.set(num, 1);
}
}
return false;
};
方法2:哈希Set
var containsDuplicate = function(nums) {
const seen = new Set();
for (const num of nums) {
if (seen.has(num)) {
return true; // 如果已经存在这个数字,说明有重复,返回true
}
seen.add(num); // 否则,将这个数字加入Set中
}
return false; // 循环结束没有发现重复,返回false
}
// 测试函数
console.log(containsDuplicate([1, 2, 3, 1])); // true
console.log(containsDuplicate([1, 2, 3, 4])); // false
console.log(containsDuplicate([1, 1, 1, 3, 3, 4, 3, 2, 4, 2])); // true
383. 赎金信 leetcode.cn/problems/ra…
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。
方法1:数组哈希
var canConstruct = function(ransomNote, magazine) {
// 检查ransomNote的长度是否大于magazine的长度,如果是,则ransomNote无法通过magazine构造,直接返回false
if (ransomNote.length > magazine.length) {
return false;
}
// 创建一个长度为26的数组,用于统计magazine中每个字母的出现次数,数组下标对应26个小写字母
const count = new Array(26).fill(0);
// 遍历magazine中的每个字符,并统计出现次数
for (const ch of magazine) {
idx = ch.charCodeAt() - 'a'.charCodeAt(); // 根据字符的ASCII码减去'a'的ASCII码,得到字符在数组中的索引
count[idx]++; // 增加该索引处的计数
}
// 遍历ransomNote中的每个字符,并检查是否可以从magazine中构造
for (const ch of ransomNote) {
idx = ch.charCodeAt() - 'a'.charCodeAt(); // 根据字符的ASCII码减去'a'的ASCII码,得到字符在数组中的索引
count[idx]--; // 减少该索引处的计数
// 使用字符'ch'之后的判断
// 如果减少计数后小于0,说明magazine中不存在足够的字符以构造ransomNote,返回false
if (count[idx] < 0) {
return false;
}
}
// 如果ransomNote可以从magazine中构造,则返回true
return true;
};
当我们将字符的ASCII码减去'a'的ASCII码时,我们实际上是将字符转换为其在字母表中的相对位置。 例如,如果我们将字符'a'的ASCII码(97)减去字符'a'的ASCII码(97),结果为0;这意味着字符'a'在字母表中的位置是0。 类似地,字符'b'减去字符'a'的结果为1,字符'c'减去字符'a'的结果为2,以此类推,直到字符'z'减去字符'a'的结果为25。
由于我们的目的是统计每个字母在magazine字符串中出现的次数,我们可以使用这个相对位置作为数组的索引。 这样一来,数组的索引0代表字母'a',索引1代表字母'b',以此类推,直到索引25代表字母'z'。 因此,我们可以将magazine字符串中的每个字符映射到对应的数组位置,并增加该位置处的计数。这样做的好处是,我们可以使用一个固定长度的数组来统计26个小写字母的出现次数,而不需要使用对象或Map来保存计数,这样可以提高代码的效率和简洁性。
方法2:对象哈希
function canConstruct(ransomNote, magazine) {
// 创建一个对象来存储 magazine 中每个字符的计数
const charCount = {};
// 遍历 magazine ,统计每个字符的出现次数
for (const ch of magazine) {
if (charCount[ch]) {
charCount[ch]++;
} else {
charCount[ch] = 1;
}
}
// 遍历 ransomNote ,检查每个字符是否可以在 magazine 中找到
for (const ch of ransomNote) {
// 使用字符'ch'之前的判断
if (!charCount[ch] || charCount[ch] === 0) {
return false; // 如果字符不存在或已用完,返回 false
}
charCount[ch]--; // 使用一个字符
}
return true; // 所有字符都能找到,返回 true
}
// 测试函数
console.log(canConstruct("a", "b")); // false
console.log(canConstruct("aa", "ab")); // false
console.log(canConstruct("aa", "aab")); // true
389. 找不同 leetcode.cn/problems/fi…
给定两个字符串 s 和 t ,它们只包含小写字母。字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。请找出在 t 中被添加的字母。
方法1:数组哈希
var findTheDifference = function(s, t) {
if (s.length === t.length) {
return false;
}
const cnt = new Array(26).fill(0);
const base = 'a'.charCodeAt(0);
// 先遍历字母数比较少的
for (const ch of s) {
cnt[ch.charCodeAt(0) - base]++;
}
// 再遍历字母数比较多的
for (const ch of t) {
cnt[ch.charCodeAt(0) - base]--;
// 找到计数值为 -1 的索引,该索引对应的字母就是多余的字母。
if (cnt[ch.charCodeAt(0) - base] < 0) { // 或cnt[ch.charCodeAt() - base] === -1
return ch;
}
}
return ' ';
};
function findTheDifference(s, t) {
const count = new Array(26).fill(0);
const base = 'a'.charCodeAt(0);
// 遍历字母数多的字符串 t,增加每个字母对应的计数。
for (const char of t) {
count[char.charCodeAt(0) - base]++;
}
// 遍历字母数少的字符串 s,减少每个字母对应的计数。
for (const char of s) {
count[char.charCodeAt(0) - base]--;
}
// 遍历计数数组,找到计数值为 1 的索引,该索引对应的字母就是多余的字母。
for (let i = 0; i < 26; i++) {
if (count[i] === 1) {
return String.fromCharCode(i + base);
}
}
return '';
}
方法2:异或运算,时间复杂度为 O(n)
利用异或运算的自反性(a ^ a = 0)来消除成对出现的字符,剩下的结果就是多出的那个字符。
异或运算(XOR)具有以下几个重要性质,这些性质使得异或运算非常适合用来处理找出不同元素的问题。
- 交换律:a ^ b = b ^ a
- 结合律:a ^ (b ^ c) = (a ^ b) ^ c
- 自反性:a ^ a = 0
- 与零的运算:a ^ 0 = a
例子解释
假设 s = "abcd" 和 t = "abcde"。
1) 初始化 charCode = 0。
2) 遍历 s:
charCode ^= 'a'.charCodeAt(0) -> charCode = 97
charCode ^= 'b'.charCodeAt(0) -> charCode = 97 ^ 98
charCode ^= 'c'.charCodeAt(0) -> charCode = (97 ^ 98) ^ 99
charCode ^= 'd'.charCodeAt(0) -> charCode = ((97 ^ 98) ^ 99) ^ 100
3) 遍历 t:
charCode ^= 'a'.charCodeAt(0) -> charCode = (((97 ^ 98) ^ 99) ^ 100) ^ 97
charCode ^= 'b'.charCodeAt(0) -> charCode = ((((97 ^ 98) ^ 99) ^ 100) ^ 97) ^ 98
charCode ^= 'c'.charCodeAt(0) -> charCode = (((((97 ^ 98) ^ 99) ^ 100) ^ 97) ^ 98) ^ 99
charCode ^= 'd'.charCodeAt(0) -> charCode = ((((((97 ^ 98) ^ 99) ^ 100) ^ 97) ^ 98) ^ 99) ^ 100
charCode ^= 'e'.charCodeAt(0) -> charCode = (((((((97 ^ 98) ^ 99) ^ 100) ^ 97) ^ 98) ^ 99) ^ 100) ^ 101
通过异或运算,相同的字符会被抵消(因为 a ^ a = 0,所以 97 ^ 97 = 0),最终只会剩下多出的字符 e 的 ASCII 码。
4) 最终 charCode 的值是 101,对应的字符是 e,所以返回 e。
function findTheDifference(s, t) {
let charCode = 0;
for (const ch of s) {
charCode ^= ch.charCodeAt(0);
}
for (const ch of t) {
charCode ^= ch.charCodeAt(0);
}
return String.fromCharCode(charCode);
}
// 测试函数
console.log(findTheDifference("abcd", "abcde")); // "e"
console.log(findTheDifference("a", "aa")); // "a"
console.log(findTheDifference("", "y")); // "y"
console.log(findTheDifference("ae", "aea")); // "a"
409. 最长回文串 leetcode.cn/problems/lo…
给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的 回文串的长度。在构造过程中,请注意 区分大小写 。比如 "Aa" 不能当做一个回文字符串。
贪心算法
1) 使用一个哈希表(对象)来记录每个字符的出现次数。
2) 遍历哈希表,计算能够添加到回文串中的字符数:
如果字符出现偶数次,则可以全部添加到回文串中。
如果字符出现奇数次,则可以添加 count - 1 个字符到回文串中,并且可以使用一个字符作为回文串的中心字符。
3) 最后,如果有中心字符,将其长度加一。
方法1:哈希Map
var longestPalindrome = function(s) {
const map = new Map();
// 统计各字符数量
for (let i = 0; i < s.length; i++) {
// charAt(index): 返回指定索引处的字符。它返回的是一个字符串,表示指定位置的字符。
ch = s.charAt(i);
if (map.has(ch)) {
map.set(ch, map.get(ch) + 1);
} else {
map.set(ch, 1);
}
}
// for (const ch of s) {
// if (map.has(ch)) {
// map.set(ch, map.get(ch) + 1);
// } else {
// map.set(ch, 1);
// }
// }
// 统计构造回文串的最大长度
let even = 0, odd = 0;
for (const [ch, count] of map) {
// 将当前字符出现次数向下取偶数,并计入 res
even += count - count % 2; // 如果是奇数,减去1;
// 若当前字符出现次数为奇数,则将 odd 置 1
if (count % 2 === 1) odd = 1;
}
return even + odd;
};
方法2:对象哈希
function longestPalindrome(s) {
const charCount = {};
// 统计每个字符的出现次数
for (const ch of s) {
if (charCount[ch]) {
charCount[ch]++;
} else {
charCount[ch] = 1;
}
}
let length = 0;
let hasOdd = false;
// 遍历字符计数
for (const count of Object.values(charCount)) {
if (count % 2 === 0) {
length += count;
} else {
length += count - 1;
hasOdd = true;
}
}
// 如果有奇数次出现的字符,可以放一个在中心位置
if (hasOdd) {
length++;
}
return length;
}
// 测试函数
console.log(longestPalindrome("abccccdd")); // 7
console.log(longestPalindrome("a")); // 1
console.log(longestPalindrome("Aa")); // 1
console.log(longestPalindrome("AaBbCcDd")); // 1
console.log(longestPalindrome("AaBbCcDdD")); // 3
268. 丢失的数字 leetcode.cn/problems/mi…
给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。
方法1:排序
时间复杂度:O(nlogn);空间复杂度:O(logn)。
var missingNumber = function(nums) {
nums.sort((a, b) => a - b);
const n = nums.length;
for (let i = 0; i < n; i++) {
if (nums[i] !== i) {
return i;
}
}
return n;
};
方法2:数学-高斯求和公式
时间复杂度:O(n);空间复杂度:O(1)。
0到n的总和 = n(n+1)/2 ,求出这个总和,然后减去数组中所有数的和,差值就是缺失的那个数。
var missingNumber = function(nums) {
const n = nums.length;
const total = n * (n + 1) / 2
let arrSum = 0;
for (let i = 0; i < n; i++) {
arrSum += nums[i];
}
// const arraySum = nums.reduce((acc, num) => acc + num, 0);
return total - arrSum;
};
方法3:Set
时间复杂度:O(n);空间复杂度:O(n)。
var missingNumber = function(nums) {
const n = nums.length;
const set = new Set(nums);
for (let i = 0; i <= n; i++) {
if (!set.has(i)) {
return i;
}
}
};
// 测试
const nums = [3, 0, 1];
console.log(missingNumber(nums)); // 输出 2
575. 分糖果 leetcode.cn/problems/di…
Alice 有 n 枚糖,其中第 i 枚糖的类型为 candyType[i] 。Alice 注意到她的体重正在增长,所以前去拜访了一位医生。 医生建议 Alice 要少摄入糖分,只吃掉她所有糖的 n / 2 即可(n 是一个偶数)。Alice 非常喜欢这些糖,她想要在遵循医生建议的情况下,尽可能吃到最多不同种类的糖。 给你一个长度为 n 的整数数组 candyType ,返回: Alice 在仅吃掉 n / 2 枚糖的情况下,可以吃到糖的 最多 种类数。
var distributeCandies = function(candyType) {
const set = new Set(candyType); // 用 Set 存储不同种类的糖
const n = candyType.length;
if (set.size <= n / 2) {
return set.size;
}
return n / 2; // Alice 可以吃的最多糖数
};
771. 宝石与石头 leetcode.cn/problems/je…
给你一个字符串 jewels 代表石头中宝石的类型,另有一个字符串 stones 代表你拥有的石头。 stones 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。 字母区分大小写,因此 "a" 和 "A" 是不同类型的石头。
方法1:哈希Map
var numJewelsInStones = function(jewels, stones) {
const map = new Map();
for (const s of stones) {
if (map.has(s)) {
map.set(s, counter.get(s) + 1);
} else {
map.set(s, 1);
}
}
let count = 0;
for (const j of jewels) {
if (map.has(j)) {
count += map.get(j);
}
}
return count;
};
方法2:哈希Set
function numJewelsInStones(jewels, stones) {
const jewelSet = new Set(jewels); // 使用 Set 存储宝石类型
let count = 0;
for (const stone of stones) {
if (jewelSet.has(stone)) {
count++;
}
}
return count;
}
// 测试
const jewels = "aA";
const stones = "aAAbbbb";
console.log(numJewelsInStones(jewels, stones)); // 输出 3
349. 两个数组的交集 leetcode.cn/problems/in…
给定两个数组 nums1 和 nums2 ,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
方法1:哈希Set
function intersection(nums1, nums2) {
// 将两个数组转换为集合
const set1 = new Set(nums1); // set1 = {1, 2} // set1 = {4, 9, 5}
const set2 = new Set(nums2); // set2 = {2} // set2 = {9, 4, 8}
// 使用过滤器来找到交集
const result = [...set1].filter(item => set2.has(item));
return result;
}
方法2:哈希Map
function intersection(nums1, nums2) {
// 创建两个 Map 来存储每个数组中的元素及其计数
const map1 = new Map();
const map2 = new Map();
// 遍历 nums1,将每个元素存储在 map1 中
for (const num of nums1) {
if (!map1.has(num)) {
map1.set(num, 1);
}
} // map1 = {['1', 1], ['2', 1]} // map1 = {['4', 1], ['9', 1], ['5', 1]}
// 遍历 nums2,将每个元素存储在 map2 中
for (const num of nums2) {
if (!map2.has(num)) {
map2.set(num, 1);
}
} // map2 = {['2', 1]} // map2 = {['9', 1], ['4', 1], ['8', 1]}
// 找到 map1 和 map2 的交集
const result = [];
// [] 用于解构赋值(destructuring assignment)。解构赋值允许我们从数组或对象中提取值,并将其赋值给变量。
for (const [key] of map1) { // const key of map1.keys()
if (map2.has(key)) {
result.push(key);
}
}
return result;
const res = new Set();
for (const num of nums2) {
if (map1.has(num)) {
res.add(num);
}
}
return [...res];
}
// 测试函数
console.log(intersection([1, 2, 2, 1], [2, 2])); // [2]
console.log(intersection([4, 9, 5], [9, 4, 9, 8, 4])); // [4, 9]
350. 两个数组的交集 II leetcode.cn/problems/in…
给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
function intersect(nums1, nums2) {
const map = new Map();
const result = [];
// 记录 nums1 中每个元素的出现次数
for (const num of nums1) {
if (map.has(num)) {
map.set(num, map.get(num) + 1);
} else {
map.set(num, 1);
}
}
// 遍历 nums2,找到交集元素并考虑出现次数
for (const num of nums2) {
if (map.has(num) && map.get(num) > 0) {
result.push(num);
map.set(num, map.get(num) - 1);
}
}
return result;
}
// 测试
const nums1 = [4, 9, 5];
const nums2 = [9, 4, 9, 8, 4];
console.log(intersect(nums1, nums2)); // 输出 [4, 9]
补充知识
使用Map对象
const myMap = new Map();
const keyString = "a string";
const keyObj = {};
const keyFunc = function () {};
// 添加键
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyObj, "和键 keyObj 关联的值");
myMap.set(keyFunc, "和键 keyFunc 关联的值");
console.log(myMap.size); // 3
// 读取值
console.log(myMap.get(keyString)); // "和键'a string'关联的值"
console.log(myMap.get(keyObj)); // "和键 keyObj 关联的值"
console.log(myMap.get(keyFunc)); // "和键 keyFunc 关联的值"
console.log(myMap.get("a string")); // "和键'a string'关联的值",因为 keyString === 'a string'
console.log(myMap.get({})); // undefined,因为 keyObj !== {}
console.log(myMap.get(function () {})); // undefined,因为 keyFunc !== function () {}
使用 for...of 迭代 Map
// Map 可以使用 for...of 循环来实现迭代:
const myMap1 = new Map();
myMap1.set(0, "zero");
myMap1.set(1, "one");
for (const [key, value] of myMap1) {
console.log(`${key} = ${value}`);
}
// 0 = zero
// 1 = one
for (const key of myMap1.keys()) {
console.log(key);
}
// 0
// 1
for (const value of myMap1.values()) {
console.log(value);
}
// zero
// one
for (const [key, value] of myMap1.entries()) {
console.log(`${key} = ${value}`);
}
// 0 = zero
// 1 = one
使用 forEach() 迭代 Map
// Map 也可以通过 forEach() 方法迭代:
myMap.forEach((value, key) => {
console.log(`${key} = ${value}`);
});
// 0 = zero
// 1 = one
Map 与数组对象的关系
const kvArray = [
["key1", "value1"],
["key2", "value2"],
];
// 使用常规的 Map 构造函数可以将一个二维的键值对数组转换成一个 Map 对象
const myMap2 = new Map(kvArray);
console.log(myMap2.get("key1")); // "value1"
// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维的键值对数组
console.log(Array.from(myMap2)); // 输出和 kvArray 相同的数组
// 更简洁的方法来做如上同样的事情,使用展开运算符
console.log([...myMap2]);
// 或者在键或者值的迭代器上使用 Array.from,进而得到只含有键或者值的数组
console.log(Array.from(myMap2.keys())); // 输出 ["key1", "key2"]
复制或合并 Map
// Map 能像数组一样被复制:
const original = new Map([[1, "one"]]);
const clone = new Map(original);
console.log(clone.get(1)); // one
console.log(original === clone); // false. 浅比较 不为同一个对象的引用
使用Set对象
const mySet1 = new Set();
mySet1.add(1); // Set(1) { 1 }
mySet1.add(5); // Set(2) { 1, 5 }
mySet1.add(5); // Set(2) { 1, 5 }
mySet1.add("some text"); // Set(3) { 1, 5, 'some text' }
const o = { a: 1, b: 2 };
mySet1.add(o);
mySet1.add({ a: 1, b: 2 }); // o 是不同对象的引用,所以这是可以的
mySet1.has(1); // true
mySet1.has(3); // false,因为并未将 3 添加到集合中
mySet1.has(5); // true
mySet1.has(Math.sqrt(25)); // true
mySet1.has("Some Text".toLowerCase()); // true
mySet1.has(o); // true
mySet1.size; // 5
mySet1.delete(5); // 从集合中移除 5
mySet1.has(5); // false,5 已从集合中移除
mySet1.size; // 4,因为我们刚刚移除了一个值
mySet1.add(5); // Set(5) { 1, 'some text', {...}, {...}, 5 }——先前删除的元素会作为新的元素被添加,不会保留删除前的原始位置
console.log(mySet1); // Set(5) { 1, "some text", {…}, {…}, 5 }
迭代Set
for (const item of mySet1) {
console.log(item);
}
// 1、"some text"、{ "a": 1, "b": 2 }、{ "a": 1, "b": 2 }、5
for (const item of mySet1.keys()) {
console.log(item);
}
// 1、"some text"、{ "a": 1, "b": 2 }、{ "a": 1, "b": 2 }、5
for (const item of mySet1.values()) {
console.log(item);
}
// 1、"some text"、{ "a": 1, "b": 2 }、{ "a": 1, "b": 2 }、5
// 键和值是相同的
for (const [key, value] of mySet1.entries()) {
console.log(key);
}
// 1、"some text"、{ "a": 1, "b": 2 }、{ "a": 1, "b": 2 }、5
// 使用 Array.from 将 Set 对象转换为数组对象
const myArr = Array.from(mySet1); // [1, "some text", {"a": 1, "b": 2}, {"a": 1, "b": 2}, 5]
// 在 Set 和 Array 之间转换
const mySet2 = new Set([1, 2, 3, 4]);
console.log(mySet2.size); // 4
console.log([...mySet2]); // [1, 2, 3, 4]
// 可以通过如下代码模拟求交集
const intersection = new Set([...mySet1].filter((x) => mySet2.has(x)));
// 可以通过如下代码模拟求差集
const difference = new Set([...mySet1].filter((x) => !mySet2.has(x)));
// 使用 forEach() 迭代集合中的条目
mySet2.forEach((value) => {
console.log(value);
});
// 1 // 2 // 3 // 4