常数时间删除/查找数组中的任意元素 :: labuladong的算法小抄 #1010
Replies: 40 comments 11 replies
-
用map来进行映射的时候,查找数字的时候,不也存在hash冲突的问题嘛,这个不是和hashset一样的嘛 |
Beta Was this translation helpful? Give feedback.
-
@jzhou3700 是存在哈希冲突,不过哈希冲突不影响时间复杂度。这道题关键在于要等概率,底层必须用紧凑存储的数组,才能做到等概率获取元素。 |
Beta Was this translation helpful? Give feedback.
-
类似LRU的hash配合double linked list也是可行的吧. val作为hash table的key, 返回随机元素就是从hash.keys( ) 里随机返回即可, 不需要遍历double linked list. |
Beta Was this translation helpful? Give feedback.
-
@alannesta 不对,和 LRU 半毛钱关系都没有,你说「从 hash.keys( ) 里随机返回」,这个功能怎么实现?不就回到本文讲的这道算法题么?标准的 hash table 根本无法提供「随机返回一个 像 Python dict 这样提供随机 pop key 的功能,是因为它的 dict 对 hash table 底层实现做了类似本题的改造。 |
Beta Was this translation helpful? Give feedback.
-
东哥,黑名单的那题。如果从一开始解题,先把思路写出来;然后一步步带着怎么得到思路感觉会好很多。 |
Beta Was this translation helpful? Give feedback.
-
黑名单看着确实有点难理解,可不可以修改数组,直接把黑名单都移动到后面,这样省着映射了。。 |
Beta Was this translation helpful? Give feedback.
-
Java Version/**
* LC380
*/
class RandomizedSet {
List<Integer> list;
HashMap<Integer, Integer> valToIndex;
Random random;
public RandomizedSet() {
list = new ArrayList<>();
valToIndex = new HashMap<>();
random = new Random();
}
public boolean insert(int val) {
if(valToIndex.containsKey(val)){
return false;
}
list.add(val);
valToIndex.put(val, list.size()-1);
return true;
}
public boolean remove(int val) {
if(!valToIndex.containsKey(val)){
return false;
}
int index = valToIndex.get(val);
int last = list.get(list.size() - 1);
list.set(index, last);
list.remove(list.size() - 1);
valToIndex.put(last, index);
valToIndex.remove(val);
return true;
}
public int getRandom() {
return list.get(random.nextInt(list.size()));
}
}
/**
* LC710
*/
class Solution {
int size = 0;
HashMap<Integer, Integer> map = new HashMap<>();
public Solution(int n, int[] blacklist) {
size = n - blacklist.length;
for(int item : blacklist){
map.put(item, -1);
}
int last = n - 1;
for(int item : blacklist){
if(item >= size){
continue;
}
while(map.containsKey(last)){
last--;
}
map.put(item, last);
last--;
}
}
public int pick() {
int index = (int)(Math.random()*size);
return map.getOrDefault(index, index);
// if(map.containsKey(index)){
// return map.get(index);
// }
// return index;
}
} |
Beta Was this translation helpful? Give feedback.
-
class Solution {
int length;
HashMap<Integer,Integer> map;
Random random;
public Solution(int n, int[] blacklist) {
random = new Random();
//因为黑名单都在n中,所以,随机数在[0,length)中取
length = n - blacklist.length;
map = new HashMap<>();
//标记黑名单
for(int b :blacklist){
map.put(b, -1);
}
int index = length;
//把黑名单中的数和length之后不是黑名单的数替换,用map记录
for(int black : blacklist){
//如果black不在[0,length)区间内 不用替换
if(0 <= black && black < length){
while(index < n){
if(!map.containsKey(index)){
//map记录
map.put(black, index);
index++;
break;
}else{
index++;
}
}
}
}
}
public int pick() {
int i = random.nextInt(length);
if(map.containsKey(i)){
return map.get(i);
}
return i;
}
} |
Beta Was this translation helpful? Give feedback.
-
js 版黑名单中的随机数 class Solution {
constructor(N, blacklist) {
this.N = N;
this.blacklist = blacklist;
const mapping = Object.create(null);
// 最终数组中的元素个数
const sz = (this.sz = N - blacklist.length);
// 先将所有黑名单数字加入 map
for (const b of blacklist) {
// 这里赋值多少都可以
// 目的仅仅是把键存进哈希表
// 方便快速判断数字是否在黑名单内
mapping[b] = -1;
}
// 最后一个元素的索引
let last = N - 1;
for (const b of blacklist) {
// 如果 b 已经在区间 [sz, N)
// 可以直接忽略
if (b >= sz) continue;
// 跳过所有黑名单中的数字
while (mapping[last] !== undefined) last--;
// 将黑名单中的索引映射到合法数字
mapping[b] = last;
last--;
}
this.mapping = mapping;
}
pick() {
const { sz, mapping } = this;
// 随机选取一个索引
const index = Math.floor(Math.random() * sz);
// 这个索引命中了黑名单,
// 需要被映射到其他位置
if (mapping[index] !== undefined) {
return mapping[index];
}
// 若没命中黑名单,则直接返回
return index;
}
} |
Beta Was this translation helpful? Give feedback.
-
直接生成一个新的 |
Beta Was this translation helpful? Give feedback.
-
@zhyozhi 这样的话会发生内存溢出的(Memory Limit Exceeded) |
Beta Was this translation helpful? Give feedback.
-
一、实现随机集合 javascript version var RandomizedSet = function() {
// 存储元素的值
this.nums = new Array();
// 存储每个元素对应在nums中的索引
this.indices = new Map();
};
/**
* @param {number} val
* @return {boolean}
*/
RandomizedSet.prototype.insert = function(val) {
// 若val已存在,不同再插入
if (this.indices.has(val)) {
return false;
}
// 若val不存在,插入到nums尾部
// 并记录val对应的索引值,保存到indices
let index = this.nums.length;
this.nums.push(val);
this.indices.set(val, index);
return true
};
/**
* @param {number} val
* @return {boolean}
*/
RandomizedSet.prototype.remove = function(val) {
// 若val不存在,不用再删除
if (!this.indices.has(val)) {
return false;
}
// 先拿到val的索引
let id = this.indices.get(val);
// 在nums中将最后一个元素覆盖val
this.nums[id] = this.nums[this.nums.length - 1];
// 在indices中更新移动元素的索引值
this.indices.set(this.nums[id], id);
// 出栈nums的尾部重复元素
this.nums.pop();
// 删除indices中val的索引值
this.indices.delete(val);
return true;
};
/**
* @return {number}
*/
RandomizedSet.prototype.getRandom = function() {
// 随机获取nums中的元素
const randomid = Math.floor(Math.random() * this.nums.length);
return this.nums[randomid];
}; |
Beta Was this translation helpful? Give feedback.
-
二、避开黑名单的随机数 javascript version var Solution = function(n, blacklist) {
// 正常元素的个数
this.length = n - blacklist.length;
this.mapping = new Map();
// 数组最后一个位置的索引指针
let last = n - 1;
// 将blacklist元素添加到mapping中
for (let b of blacklist) {
this.mapping.set(b, 0);
}
// 修改[0, sz)内的blacklist元素映射索引,映射至[sz, n-1]的正常元素索引
for (let b of blacklist) {
if (b < this.length) {
while (this.mapping.has(last)){
last--;
}
// 将blacjlist中的元素索引映射到last
this.mapping.set(b, last);
last--;
}
}
};
/**
* @return {number}
*/
Solution.prototype.pick = function() {
// 随机生成正常的索引值
var index = Math.floor(Math.random() * this.length);
// 若该索引值是blacklist中的元素,映射到正常元素
if (this.mapping.has(index)) {
return this.mapping.get(index);
}
// 若该索引值是blacklist中的元素,返回正常元素的值
return index;
}; |
Beta Was this translation helpful? Give feedback.
-
想问问为什么先put再remove与remove后再put结果会不一样 |
Beta Was this translation helpful? Give feedback.
-
题目一:来一个PHP版本的 class RandomizedSet {
public $values = [];
public $indexs = [];
public function insert($val) {
if(isset($this->indexs[$val])) {
return false;
}
$this->values[] = $val;
$this->indexs[$val] = count($this->values) - 1;
return true;
}
public function remove($val) {
if(!isset($this->indexs[$val])) {
return false;
}
//获取删除元素索引
$idx = $this->indexs[$val];
//获取数组末尾元素,用于和删除元素替换
$lastItem = $this->values[count($this->values)-1];
//末尾元素的索引更新为删除元素索引
$this->indexs[$lastItem] = $idx;
//删除元素索引对应的值修改为末尾元素的值
$this->values[$idx] = $lastItem;
$this->values[count($this->values)-1] = $val;//可以不用赋值,反正后面都要删掉的
//删除末尾元素的值和索引
array_pop($this->values);
unset($this->indexs[$val]);
return true;
}
public function getRandom() {
if(empty($this->values)) {
return false;
}
return $this->values[rand(0,count($this->values) - 1)];
}
} |
Beta Was this translation helpful? Give feedback.
-
打卡 黑名单,蛮难的呀。 |
Beta Was this translation helpful? Give feedback.
-
unsorted_map的count方法不也是O(n)的吗,那这样的插入和删除为什么时间复杂度是O(1)呢 |
Beta Was this translation helpful? Give feedback.
-
380 type RandomizedSet struct {
orderMap map[int]int
nums []int
}
func Constructor() RandomizedSet {
return RandomizedSet{ map[int]int{},[]int{}}
}
func (this *RandomizedSet) Insert(val int) bool {
_,ok:=this.orderMap[val]
if ok {
return false
}
this.orderMap[val] = len(this.nums)
this.nums = append(this.nums,val)
return true
}
func (this *RandomizedSet) Remove(val int) bool {
idx,ok:=this.orderMap[val]
if !ok{
return false
}
last := len(this.nums) - 1
//交换
this.orderMap[this.nums[last]] = idx
tmp:=this.nums[idx]
this.nums[idx] = this.nums[last]
this.nums[last] = tmp
this.nums = this.nums[:last]
delete(this.orderMap,val)
return true
}
func (this *RandomizedSet) GetRandom() int {
return this.nums[rand.Intn(len(this.nums))]
}
/**
* Your RandomizedSet object will be instantiated and called as such:
* obj := Constructor();
* param_1 := obj.Insert(val);
* param_2 := obj.Remove(val);
* param_3 := obj.GetRandom();
*/ |
Beta Was this translation helpful? Give feedback.
-
看题解之前憋出来一个版本(不过速度非常慢):把黑名单里的数字 from random import randint
class Solution:
def __init__(self, n: int, blacklist: List[int]):
balcklist = set(blacklist)
self.upper = n-1
self.blacklisted_mapping = dict()
remapped = 0
cache = []
cache_p = 0
use_cache = False
for e in blacklist:
while not use_cache:
if remapped >= n:
use_cache = True
break
elif remapped in blacklist:
remapped += 1
else:
break
if use_cache:
remapped = cache[cache_p]
cache_p += 1
if cache_p == len(cache):
cache_p = 0
self.blacklisted_mapping[e] = remapped
if not use_cache:
cache.append(remapped)
remapped += 1
def pick(self) -> int:
e = randint(0, self.upper)
if e in self.blacklisted_mapping:
return self.blacklisted_mapping[e]
else:
return e |
Beta Was this translation helpful? Give feedback.
-
20230110打卡,这映射太巧妙 |
Beta Was this translation helpful? Give feedback.
-
710题黑名单数字问题:n=4, blackList=[2,1], 数字2会映射到索引3,数字1会映射到索引0,此时sz=2,随机数只能是0和1,随机到0会直接返回0,随机到1还是会返回0,这样标准答案会判定你的数字不是等概率返回的。 |
Beta Was this translation helpful? Give feedback.
-
我有个问题请教下东哥, |
Beta Was this translation helpful? Give feedback.
-
if (b >= sz) { |
Beta Was this translation helpful? Give feedback.
-
if (b >= sz) { |
Beta Was this translation helpful? Give feedback.
-
问个问题:if (mapping.containsKey(index)) { |
Beta Was this translation helpful? Give feedback.
-
class RandomizedSet {
constructor() {
this.map = new Map();
this.arr = [];
}
insert(val) {
if (this.map.has(val)) return false;
this.arr.push(val);
this.map.set(val, this.arr.length - 1);
return true;
}
remove(val) {
if (!this.map.has(val)) return false;
const index = this.map.get(val);
const last = this.arr.pop();
this.arr[index] = last;
return true;
}
getRandom() {
return this.arr[Math.floor(Math.random() * this.arr.length)];
}
} |
Beta Was this translation helpful? Give feedback.
-
第27 28行 请问为什么要先修改哈希表,再交换元素位置啊。把这两行放到29行交换位置之后,为啥会出问题呢? |
Beta Was this translation helpful? Give feedback.
-
mapping.count 应该为containsKey |
Beta Was this translation helpful? Give feedback.
-
为什么这样会超时呢,感觉复杂度没什么变化。
|
Beta Was this translation helpful? Give feedback.
-
文章链接点这里:常数时间删除/查找数组中的任意元素
评论礼仪 见这里,违者直接拉黑。
Beta Was this translation helpful? Give feedback.
All reactions