Leetcode 380. 常数时间插入、删除和获取随机元素(中等) 710. 黑名单中的随机数(困难) 使用数组实现随机查找以及等概率随机返回

labuladong讲解

380. 常数时间插入、删除和获取随机元素(中等)

题目:

 

 题目理解:

就是说就是让我们实现如下一个类:

class RandomizedSet {
public:
    /** 如果 val 不存在集合中,则插入并返回 true,否则直接返回 false */
     bool insert(int val) {}
    
    /** 如果 val 在集合中,则删除并返回 true,否则直接返回 false */
    bool remove(int val) {}
    
    /** 从集合中等概率地随机获得一个元素 */
    int getRandom() {}
}

思路:

底层用数组实现,且数组必须是紧凑的。

这样我们就可以直接生成随机数作为索引,从数组中取出该随机索引对应的元素,作为随机元素。

如果我们想在 O(1) 的时间删除数组中的某一个元素 val,可以先把这个元素交换到数组的尾部,然后再 pop 掉。

交换两个元素必须通过索引进行交换对吧,那么我们需要一个哈希表 valToIndex 来记录每个元素值对应的索引。

class RandomizedSet {
public:
    RandomizedSet() {

    }
    
    bool insert(int val) {
        // 若 val 已存在,不用再插入
        if(m.count(val)>0){
            return false;
        }
        // 若 val 不存在,插入到 nums 尾部,
        // 并记录 val 对应的索引值
        m[val]=num.size();
        num.push_back(val);
        return true;
    }
    
    bool remove(int val) {
        // 若 val 不存在,不用再删除
        if(m.count(val)==0){
            return false;
        }
        // 先拿到 val 的索引
        int index=m[val];
        // 将最后一个元素对应的索引修改为 index
        m[num.back()]=index;
        // 交换 val 和最后一个元素
        swap(num[index],num[num.size()-1]);
        // 在数组中删除元素 val
        num.pop_back();
        // 删除元素 val 对应的索引
        m.erase(val);
        return true;
    }
    
    int getRandom() {
        // 随机获取 nums 中的一个元素
        return num[rand()%num.size()];
    }
    // 存储元素的值
    vector<int> num;
    // 记录每个元素对应在 nums 中的索引
    unordered_map<int,int> m;
};

 

710. 黑名单中的随机数(困难)

题目:

给你输入一个正整数 N,代表左闭右开区间 [0,N),再给你输入一个数组 blacklist,其中包含一些「黑名单数字」,且 blacklist 中的数字都是区间 [0,N) 中的数字。

现在要求你设计如下数据结构:

class Solution {
public:
    // 构造函数,输入参数
    Solution(int N, vector<int>& blacklist) {}
    
    // 在区间 [0,N) 中等概率随机选取一个元素并返回
    // 这个元素不能是 blacklist 中的元素
    int pick() {}
};

pick 函数会被多次调用,每次调用都要在区间 [0,N) 中「等概率随机」返回一个「不在 blacklist 中」的整数。

这应该不难理解吧,比如给你输入 N = 5, blacklist = [1,3],那么多次调用 pick 函数,会等概率随机返回 0, 2, 4 中的某一个数字。

而且题目要求,在 pick 函数中应该尽可能少调用随机数生成函数 rand()

 

思路:

我们可以将区间 [0,N) 看做一个数组,然后将 blacklist 中的元素移到数组的最末尾,同时用一个哈希表进行映射

相当于把黑名单中的数字都交换到了区间 [sz, N) 中,同时把 [0, sz) 中的黑名单数字映射到了正常数字。

映射时需要注意,如果黑名单数组本身就在[sz,N)中,则可以直接跳过,不用保存到哈希表中。同时,映射数字也需要跳过。

 

class Solution {
public:
    Solution(int n, vector<int>& blacklist) {
        // 先将所有黑名单数字加入 map
        for(int i=0;i<blacklist.size();++i){
            // 这里赋值多少都可以
            // 目的仅仅是把键存进哈希表
            // 方便快速判断数字是否在黑名单内
            m[blacklist[i]]=666;
        }
        int last=n-1;
        sz=n-blacklist.size();
        for(int i=0;i<blacklist.size();++i){
            // 如果 b 已经在区间 [sz, N)
            // 可以直接忽略
            if(blacklist[i]>=sz){
                continue;
            }
            // 跳过所有黑名单中的数字
            while(m.count(last)>0){
                last--;
            }
            // 将黑名单中的索引映射到合法数字
            m[blacklist[i]]=last;
            last--;
        }
    }
    
    int pick() {
        // 随机选取一个索引
        int index=rand()%sz;
        // 这个索引命中了黑名单,
        // 需要被映射到其他位置
        if(m.count(index)>0)
            return m[index];
        // 若没命中黑名单,则直接返回
        return index;
    }
    unordered_map<int,int> m;
    int sz;
};

 

posted @ 2022-02-28 15:06  鸭子船长  阅读(58)  评论(0编辑  收藏  举报