题目:
题目理解:
就是说就是让我们实现如下一个类:
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; };
题目:
给你输入一个正整数 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; };