今天做的是 381. O(1) 时间插入、删除和获取随机元素 - 允许重复,在做这道题之前,我们先来看一下这道题的低级版:380. 常数时间插入、删除和获取随机元素 。知道了 0380 怎么做,在这个基础上把键值对的值改成Set<Integer>就好办了~

0380 常数时间插入、删除和获取随机元素

380. 常数时间插入、删除和获取随机元素
难度中等
设计一个支持在 平均 时间复杂度 O(1) 下,执行以下操作的数据结构。
insert(val):当元素 val 不存在时,向集合中插入该项。
remove(val):元素 val 存在时,从集合中移除该项。
getRandom:随机返回现有集合中的一项。每个元素应该有 相同的概率被返回。
示例 :



// 初始化一个空的集合。
RandomizedSet randomSet = new RandomizedSet();
// 向集合中插入 1 。返回 true 表示 1 被成功地插入。
randomSet.insert(1);
// 返回 false ,表示集合中不存在 2 。
randomSet.remove(2);
// 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。
randomSet.insert(2);
// getRandom 应随机返回 1 或 2 。
randomSet.getRandom();
// 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。
randomSet.remove(1);
// 2 已在集合中,所以返回 false 。
randomSet.insert(2);
// 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。
randomSet.getRandom();



看到这题,就觉得似曾相识,以前在上公开课,写作业的时候不就写过这一题嘛?

题目代码

核心思想是,用数组存储所有元素,元素排放顺序无所谓,反正如果删除的话就和尾部元素换个位置就行了

这里也是一样。

  • int size记录所有元素数量。
  • int[] nums 存储所有元素,初始化成较长的数组--目的是避免扩容(一般来说题目应该给出对RandomizedSet的操作次数,但这里没给)。添加元素的时候在nums的尾部添加,删除元素时和尾部元素交换,size--。
  • Map<Integer,Integer> map 记录 (内容nums[i],下标i)键值对。




java hashmap删除第一个 map删除第一个元素_oracle获取时间最大的一条数据


class RandomizedSet {

    Map<Integer, Integer> map=new HashMap<>();
    int[] nums=new int[10000];
    int size=0;
    /** Initialize your data structure here. */
    public RandomizedSet() {

    }
    
    /** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
    public boolean insert(int val) {
        if(map.containsKey(val)) return false;
        nums[size++]=val;
        map.put(val, size-1);
        return true;
    }
    
    /** Removes a value from the set. Returns true if the set contained the specified element. */
    public boolean remove(int val) {
        if(!map.containsKey(val)) return false;
        int pos=map.remove(val);
        if(pos!=size-1){
            nums[pos]=nums[size-1];
            map.put(nums[pos], pos);
        }
        size--;
        return true;
    }
    
    /** Get a random element from the set. */
    public int getRandom() {
        return nums[(int) (Math.random()*size)];
    }
}

/**
 * Your RandomizedSet object will be instantiated and called as such:
 * RandomizedSet obj = new RandomizedSet();
 * boolean param_1 = obj.insert(val);
 * boolean param_2 = obj.remove(val);
 * int param_3 = obj.getRandom();
 */


0381 O(1)时间插入、删除和获取随机元素 -允许重复

381. O(1) 时间插入、删除和获取随机元素 - 允许重复
难度困难150
设计一个支持在 平均 时间复杂度 O(1) 执行以下操作的数据结构。 注意: 允许出现重复元素。 insert(val):向集合中插入元素 val。 remove(val):当 val 存在时,从集合中移除一个 val。 getRandom:从现有集合中随机获取一个元素。每个元素被返回的概率应该与其在集合中的数量呈线性相关。
示例:


// 初始化一个空的集合。 
RandomizedCollection collection = new RandomizedCollection();  
// 向集合中插入 1 。返回 true 表示集合不包含 1 。 
collection.insert(1);  
// 向集合中插入另一个 1 。返回 false 表示集合包含 1 。集合现在包含 [1,1] 。 collection.insert(1);  
// 向集合中插入 2 ,返回 true 。集合现在包含 [1,1,2] 。 
collection.insert(2);  
// getRandom 应当有 2/3 的概率返回 1 ,1/3 的概率返回 2 。 
collection.getRandom();  
// 从集合中删除 1 ,返回 true 。集合现在包含 [1,2] 。 
collection.remove(1);  
// getRandom 应有相同概率返回 1 和 2 。 
collection.getRandom();


这道题和上面的区别在于,允许重复值了。那么显然用Integer来存储值在nums中的位置就不现实了,这里就得用Set<Integer>来存储。

这道题,我们用这些数据结构。

  • int size 作用同上。
  • int[] nums 作用同上。
  • Map<Integer, Set<Integer>> map 记录 (内容nums[i],下标i )键值对。因为允许重复值,所以同一个元素可能对应多个位置。


java hashmap删除第一个 map删除第一个元素_set获取元素_02


class RandomizedCollection {
    // Map<Integer, Integer> map=new HashMap<>();
    Map<Integer, Set<Integer>> map=new HashMap<>();
    int[] nums=new int[10000];
    int size=0;

    /** Initialize your data structure here. */
    public RandomizedCollection() {

    }
    
    /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */
    public boolean insert(int val) {
        boolean flag=true;
        if(map.containsKey(val)) flag=false;
        nums[size++]=val;
        Set<Integer> set=map.getOrDefault(val, new HashSet<Integer>());
        set.add(size-1);
        map.put(val, set);
        return flag;
    }
    
    /** Removes a value from the collection. Returns true if the collection contained the specified element. */
    public boolean remove(int val) {
        if(!map.containsKey(val)) return false;
        Iterator<Integer> it=map.get(val).iterator();
        int pos=it.next();
        map.get(val).remove(pos);
        if(pos!=size-1){
            nums[pos]=nums[size-1];
            Set<Integer> set=map.get(nums[pos]);
            set.remove(size-1);
            set.add(pos);
            map.put(nums[pos], set);
        }
        if(map.get(val).size()==0) map.remove(val);
        size--;
        return true;
    }
    
    /** Get a random element from the collection. */
    public int getRandom() {
        return nums[(int) (Math.random()*size)];
    }
}

/**
 * Your RandomizedCollection object will be instantiated and called as such:
 * RandomizedCollection obj = new RandomizedCollection();
 * boolean param_1 = obj.insert(val);
 * boolean param_2 = obj.remove(val);
 * int param_3 = obj.getRandom();
 */


数据结构讨论

Q1:可以用Map<Integer, List<Integer>> 吗?

A1:可以。但是要注意一个地方,remove的时候要转成 Integer,例如:


map.get(val).remove((Integer) pos);


查看List的API,可见


java hashmap删除第一个 map删除第一个元素_set获取元素_03


对于Set而言,remove会自动装箱为Integer。但是对于List,如果输入整型变量,那么默认调用的是第一个函数,而不是第二个函数。

这个改为List的代码我贴在GitHub上了,戳 这里 即可查看。速度比Set快1ms(可以忽略)。


java hashmap删除第一个 map删除第一个元素_set获取元素_04


Q2:nums可以用List<Integer>代替吗?

A2:当然可以。但是List的坏处就在于,它可能需要扩容多次,比较浪费时间。所以我干脆设计一个超长的nums免去扩容的麻烦。事实上,官方解答 就是用List<Integer>做的,但是速度比数组要慢啊。


java hashmap删除第一个 map删除第一个元素_set获取元素_05


欢迎点赞、评论、分享本文并且关注我的GitHub,已收录200+题解。

你所有的互动都是我坚持写下去的动力,谢谢。

以上。


java hashmap删除第一个 map删除第一个元素_oracle 查询数据插入时间_06