JavaScript数据结构与算法——字典

105 阅读5分钟

字典

  • 与集合类似,字典也是一种存储唯一值的数据结构,但它是以 键值对 (集合是 [值 值])的形式来存储
  • ES6 中有字典,名为 Map
  • 字典的常用操作:键值对的增删改查

LeetCode:349 两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]

输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]

输出:[9,4]

解释:[4,9] 也是可通过的

解题思路:

  • nums1nums2 都有的值
  • 用字典建立一个映射关系,记录 nums1 里有的值
  • 遍历 nums2,找出 nums1 里面也有的值

解题步骤:

  • 新建一个字典,遍历 nums1,填充字典
  • 遍历 nums2,遇到字典里面的字就选出,并从字典中删除
/**
 * @param {number[]} nums1
 * @param {number[]} nums2
 * @return {number[]}
 */
var intersection = function(nums1, nums2) {
    const map = new Map();
    nums1.forEach(n => {
        map.set(n, true);
    });
    const res = [];
    nums2.forEach(m => {
        if(map.get(m)) {
            res.push(m);
            map.delete(m);
        }
    });
    return res;
};

时间复杂度:O(n + m)

空间复杂度:O(n)

LeetCode:20. 有效的括号

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

示例 1:

输入:s = "()"

输出:true

示例 2:

输入:s = "()[]{}"

输出:true

示例 3:

输入:s = "(]"

输出:false

用字典简化相关操作

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
    if (s.length % 2 === 1) return false;
    let stack = [];
    const map = new Map();
    map.set('(', ')');
    map.set('{', '}');
    map.set('[', ']');
    for (let i of s) {
        // map.has - 判断 key 是否在 map 中
        if (map.has(i)) {
            stack.push(i)
        } else {
            let top = stack[stack.length - 1];
            if (map.get(top) === i) {
                stack.pop()
            } else {
                return false
            }
        }
    }
    return stack.length === 0;
};

时间复杂度:O(n)

空间复杂度:O(n)

LeetCode:1. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9 ​

输出:[0,1]

解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6

输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6

输出:[0,1]

解题思路:

  • nums 逐个遍历
  • target 是匹配条件
  • 建立一个字典,存储相亲者的数字和下标

解题步骤:

  • 新建一个字典
  • nums 里面的值,逐个匹配,没有合适的就先放在字典中,有合适的就输出
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    let map = new Map();
    for(let i = 0; i < nums.length; i++) {
        if(map.has(target - nums[i])) {
            return [map.get(target - nums[i]), i]
        }else {
            map.set(nums[i], i);
        }
    }
};

时间复杂度:O(n)

空间复杂度:O(n)

LeetCode:3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: s = "abcabcbb"

输出: 3

解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: s = "bbbbb"

输出: 1

解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: s = "pwwkew"

输出: 3

解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串(完整的剪切)。

解题思路:

  • 先找出所有的不包含重复字符的子串
  • 找出长度最大的那个子串,返回其长度即可

解题步骤:

  • 用双指针维护一个滑动窗口,用来剪切子串
  • 不断移动右指针,遇到重复字符,就把左指针移动到重复字符的下一位
/**
 * @param {stringtring} string
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    let l = 0;
    let res = 0;
    const map = new Map();
    for (let r = 0; r < s.length; r++) {
        // 遇到重复字符,就把左指针移动到重复字符的下一位
        if (map.has(s[r]) && map.get(s[r]) >= l) { // 重复字符必须在滑动窗口中
            l = map.get(s[r]) + 1;
        }
        res = Math.max(res, r - l + 1);
        // 移动右指针,并把字符以及相应下标存入字典中
        map.set(s[r], r);
    }
    return res;
};

时间复杂度:O(n)

空间复杂度:O(m) 其中 m 是字符串中不重复字符个数

LeetCode:76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"

输出:"BANC"

解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a"

输出:"a"

解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa"

输出: ""

解释: t 中两个字符 'a' 均应包含在 s 的子串中, 因此没有符合条件的子字符串,返回空字符串。

解题思路:

  • 先找出所有包含 T 的子串
  • 找出长度最小的那个子串,返回即可

解题步骤:

  • 用双指针维护一个滑动窗口
  • 移动右指针,找到包含 T 的子串后,再移动左指针,尽量减少包含 T 的子串的长度
  • 循环上述过程,找到最小子串
/**
 * @param {string} s
 * @param {string} t
 * @return {string}
 */
var minWindow = function(s, t) {
    let l = 0;
    let r = 0;
    let need = new Map();
    for (let c of t) {
        // 存储 t 中的字符以及个数
        need.set(c, need.has(c) ? need.get(c) + 1 : 1)
    }
    let needType = need.size;
    let res = '';
    while (r < s.length) {
        // 移动右指针,找到包含 T 的子串
        const c = s[r];
        if (need.has(c)) {
            need.set(c, need.get(c) - 1);
            if (need.get(c) === 0) needType -= 1;
        }
        while (needType === 0) {
            const newRes = s.substring(l, r + 1);
            if (!res || newRes.length < res.length) res = newRes;
            // 移动左指针,尽量减少包含 T 的子串的长度
            const c2 = s[l];
            if (need.has(c2)) {
                need.set(c2, need.get(c2) + 1);
                if (need.get(c2) === 1) needType += 1;
            }
            l += 1;
        }
        r += 1;
    }
    return res;
};

时间复杂度:O(m+n) 其中 mt 的长度, ns 的长度

空间复杂度:O(m)