小哆啦解题记:实现一个名为 `RandomizedSet` 的类

110 阅读4分钟

小哆啦开始力扣每日一题的第十一天

leetcode.cn/problems/in…

《小哆啦解题记:实现一个名为 RandomizedSet 的类》

在编程王国里,有一位名叫小哆啦的年轻程序员。一天,他接到了一项挑战:实现一个名为 RandomizedSet 的类,这个类需要支持以下操作:

  1. insert(val) :如果元素 val 不在集合中,向集合中插入该元素并返回 true,否则返回 false
  2. remove(val) :如果元素 val 在集合中,删除该元素并返回 true,否则返回 false
  3. getRandom() :随机返回集合中的一个元素。

但是,还有一个额外的要求:每个操作的平均时间复杂度必须是 O(1)。

第一个尝试:O(n) 的方法

小哆啦开始思考,最直观的方法就是使用一个 数组 来存储所有的元素,然后实现相应的操作。

  • 对于 insert,我们只需要将元素添加到数组末尾,并判断该元素是否已经存在,这个操作的时间复杂度是 O(n)。
  • 对于 remove,如果我们直接遍历数组找到元素并删除它,时间复杂度也是 O(n)。
  • 对于 getRandom,从数组中随机取一个元素可以在 O(1) 时间内完成。

小哆啦写下了以下代码:

class RandomizedSet {
    private values: number[];

    constructor() {
        this.values = [];
    }

    insert(val: number): boolean {
        if (this.values.includes(val)) {
            return false;  // 如果元素已存在,返回 false
        }
        this.values.push(val);
        return true;
    }

    remove(val: number): boolean {
        const index = this.values.indexOf(val);
        if (index === -1) {
            return false;  // 如果元素不存在,返回 false
        }
        this.values.splice(index, 1);  // 删除指定元素
        return true;
    }

    getRandom(): number {
        const randomIndex = Math.floor(Math.random() * this.values.length);
        return this.values[randomIndex];
    }
}

调试与分析

小哆啦很快就发现了问题:insertremove 操作的时间复杂度竟然是 O(n)。特别是在进行删除时,数组中的元素需要移动,splice 操作会花费大量时间。而且,随着集合元素的增加,性能问题会变得越来越严重。

“这样下去不行!我需要找个更高效的解决方案。” 小哆啦自言自语。

优化方案:从 O(n) 到 O(1)

经过一番冥思苦想,小哆啦突然灵机一动——哈希表!如果使用一个哈希表来记录每个元素的索引,那么就可以在 O(1) 的时间内判断元素是否存在,并在 O(1) 时间内插入和删除元素。

为了进一步优化,数组仍然用来存储元素,哈希表的作用仅仅是提供快速查找和删除。

改进后的代码:

class RandomizedSet {
    private valToIndex: Map<number, number>;  // 值到索引的映射
    private values: number[];  // 用于存储所有的值

    constructor() {
        this.valToIndex = new Map();
        this.values = [];
    }

    // 插入元素
    insert(val: number): boolean {
        if (this.valToIndex.has(val)) {
            return false;  // 如果元素已存在,返回 false
        }
        // 插入新元素到数组末尾,并更新哈希表中的映射
        this.valToIndex.set(val, this.values.length);
        this.values.push(val);
        return true;
    }

    // 删除元素
    remove(val: number): boolean {
        if (!this.valToIndex.has(val)) {
            return false;  // 如果元素不存在,返回 false
        }

        // 获取要删除元素的索引
        const index = this.valToIndex.get(val)!;
        // 获取数组中的最后一个元素
        const lastVal = this.values[this.values.length - 1];
        
        // 将最后一个元素移到要删除的元素位置
        this.values[index] = lastVal;
        this.valToIndex.set(lastVal, index);

        // 删除数组中的最后一个元素
        this.values.pop();
        this.valToIndex.delete(val);
        return true;
    }

    // 获取随机元素
    getRandom(): number {
        const randomIndex = Math.floor(Math.random() * this.values.length);
        return this.values[randomIndex];
    }
}

优化思路:

  1. insert(val)

    • 使用哈希表 valToIndex 来检查元素是否已经存在,查找时间复杂度是 O(1)。
    • 将元素插入到 values 数组末尾,插入操作 O(1)。
  2. remove(val)

    • 先通过哈希表快速查找元素的位置。
    • 将数组中的最后一个元素移动到要删除的位置,保持数组连续性,时间复杂度 O(1)。
    • 删除数组的最后一个元素,时间复杂度 O(1)。
  3. getRandom()

    • values 数组中随机选择一个元素,时间复杂度 O(1)。

结果与总结

经过小哆啦的努力,他终于将 RandomizedSet 的操作时间复杂度从 O(n) 优化到 O(1)。通过巧妙地结合使用哈希表和数组,他成功地解决了这个挑战,成为了编程王国的英雄。

小哆啦对自己充满了信心:“编程的世界里,任何问题都有最优的解决方案。只要冷静分析,灵感总会到来!”