阅读 26

学习JavaScript数据结构与算法之集合与字典(4)

1. 什么是集合

  • 由一组无序且唯一(即不能重复)的项组成的
  • 可以把集合想象成一个既没有重复元素,也没有顺序概念的数组

常用操作:

  • 去重
  • 判断某元素是否在集合中
  • 求交集/差集/并集
// 去重
const arr = [1, 1, 2, 2];
const arr2 = [...new Set(arr)];

// 判断元素是否在集合中
const set = new Set(arr);
const has = set.has(3);

// 求交集
const set2 = new Set([2, 3]);
const set3 = new Set([...set].filter(item => set2.has(item)));
复制代码

2. 实现一个集合

  • add(element):向集合添加一个新元素。
  • delete(element):从集合移除一个元素。
  • has(element):如果元素在集合中,返回 true,否则返回 false。  clear():移除集合中的所有元素。
  • size():返回集合所包含元素的数量。它与数组的 length 属性类似。
  • values():返回一个包含集合中所有值(元素)的数组。
class Set {
    constructor () {
        this.items = {}
    }

    has (element) {
        return Object.prototype.hasOwnProperty.call(this.items, element)
    }

    add (element) {
        if (!this.has(element)) {
            this.items[element] = element
            return true
        }
        return false
    }

    delete (element) {
        if (this.has(element)) {
            delete this.items[element]
            return true
        }
        return false
    }

    clear () {
        this.items = {}
    }

    size () {
        return Object.keys(this.items).length
    }

    values () {
        return Object.values(this.items)
    }
}
复制代码

3. 集合运算

  • 并集:对于给定的两个集合,返回一个包含两个集合中所有元素的新集合。
  • 交集:对于给定的两个集合,返回一个包含两个集合中共有元素的新集合。
  • 差集:对于给定的两个集合,返回一个包含所有存在于第一个集合且不存在于第二个集合的元素的新集合。
  • 子集:验证一个给定集合是否是另一集合的子集。

3.1 求并集

let union = a.concat(b.filter(v => !a.includes(v)))

new Set([...setA, ...setB])
复制代码

3.2 求交集

解题思路: 求交集且无序唯一。

解题步骤:

    1. 用集合对num1去重
    1. 遍历nums1,筛选出nums2也包含的值

时间复杂度filter+includesO(m*n), 空间复杂度O(m)

var intersection = function(nums1, nums2) {
    return [...new Set(nums1)].filter(n => nums2.includes(n))
};
复制代码

3.3 求差集

let difference = a.concat(b).filter(v => a.includes(v) && !b.includes(v))
new Set([...setA].filter(x => !setB.has(x)))
复制代码

3.4 求子集

var intersection = function(nums1, nums2) {
    if (nums2.length < nums1.length) {
        return false
    }

    return nums1.every(value => {
        return nums2.include(value)
    })
}
复制代码

4. 什么是字典

字典和集合很相似,集合以[值,值]的形式存储元素,字典则是以[键,值]的形式来存储元素。字典也称作映射、符号表或关联数组

5. 字典常用操作

5.1 求交集

解题思路:

    1. 求nums1和nums2都有的值。
    1. 用字典建立一个映射关系,记录nums1里有的值。

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

解题步骤:

    1. 新建一个字典,遍历nums1, 填充字典
    1. 遍历nums2,遇到字典里的值就选出,并从字典中删除
var intersection = function(nums1, nums2) {
    const map = new Map();
    nums1.forEach(n => {
        map.set(n, true)
    })
    const res = []
    nums2.forEach(n => {
        if(map.get(n)){
            res.push(n)
            map.delete(n)
        }
    })
    return res;
}
复制代码

5.2 两数之和

解题思路:

    1. 把nums想象成相亲者
    1. 把target想象成匹配条件
    1. 用字典建立一个婚姻介绍所,存储相亲者的数字和下标

解题步骤:

    1. 新建一个字典作为婚姻介绍所
    1. nums里的值,逐个来介绍所找对象,没有合适的就先登记着,有合适的就牵手成功

时间复杂度O(n), 空间复杂度O(n)

var twoSum = function(nums, target) {
    const map = new Map()
    for(let i = 0; i < nums.length; i+=1) {
        const n = nums[i]
        const n2 = target - n
        if(map.has(n2)) {
            return [map.get(n2), i]
        } else {
            map.set(n, i)
        }
    }
};
复制代码

5.3 无重复字符的最长子串

解题思路:

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

解题步骤:

    1. 用双指针维护一个滑动窗口,用来剪切子串
    1. 不断移动右指针,遇到重复字符,就把左指针移动到重复字符的下一位
    1. 过程中,记录所有窗口的长度,并返回最大值

时间复杂度O(n), 空间复杂度O(m),字符串中不重复字符串的个数

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
};
复制代码

5.4 最小覆盖子串

解题思路:

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

解题步骤:

    1. 用双指针维护一个滑动窗口
    1. 移动右指针,找到包含T的子串,移动左指针,尽量减少包含T的字串的长度
    1. 循环上述过程,找出包含T的最小子串

时间复杂度O(m+n), m是t的长度,n是s的长度,空间复杂度O(m)

var minWindow = function(s, t) {
    let l = 0;
    let r = 0;
    const need = new Map();
    for(let c of t) {
        need.set(c, need.has(c) ? need.get(c) + 1 : 1)
    }
    let needType = need.size;
    let res = ''
    while(r < s.length) {
        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
            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;
};
复制代码