[LC] 381. Insert Delete GetRandom O(1) - Duplicates allowed

本文深入探讨了一种允许重复元素的随机集合数据结构实现,通过使用ArrayList和HashMap配合LinkedHashSet,确保了元素插入、删除和随机获取操作的高效执行。特别关注了如何处理重复元素及其概率加权随机选择。

 

这一题和https://blog.csdn.net/chaochen1407/article/details/102067676 只有两行之差。Duplicates allowed和linear related。这意思就是我可以多次插入一个元素并且根据我插入的次数它被random picked的概率也会相对应提高。譬如我insert(1) insert(2) insert(2),在这种情况下,我getRandom的时候,获得2的概率就是66%而获得1的概率就是33%。 在leetcode的题集里,往往改掉一个条件就能改变一个题目的性质,然而这题不是。这题的中心思想依旧和之前那题是差不多的。就是利用HashMap记录数字出现的位置和ArrayList装数据并且作getRandom处理,每当需要插入的时候,就往数组末尾插入,并且更新HashMap相关的位置信息,每当需要删除的时候,就和末尾的元素进行交换并且删除。只是因为duplicates allowed了,所以我们不能再用HashMap<Integer, Integer>这样的结构来记录一个数字的位置,相对应的,我们需要把它变成HashMap<Integer, Set<Integer>>,用set来装一堆位置而不仅仅是一个。每次,我们删除的时候,我们需要从Set里面选取一个元素进行交换或者删除,而不只是淡淡从map中删除掉数值对应的位置,只有当数值对应的Set为空的时候,我们就会知道当前数据里已经没有了那个数值,可以将Set本身也进行删除。先贴出代码,要注意代码中我们用到了一个新的数据结构LinkedHashSet,一会儿解释这是啥和为什么用这个。

class RandomizedCollection {
    
    ArrayList<Integer> dataArr;
    HashMap<Integer, Set<Integer>> indexMap;
    Random random;
    /** Initialize your data structure here. */
    public RandomizedCollection() {
        dataArr = new ArrayList<>();
        indexMap = new HashMap<>();
        random = new Random();
    }
    
    /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */
    public boolean insert(int val) {
        dataArr.add(val);
        Set<Integer> indexSet = null;
        boolean result = true;
        if (!indexMap.containsKey(val)) {
            indexSet = new LinkedHashSet<>();
            indexMap.put(val, indexSet);
        } else {
            result = false;
            indexSet = indexMap.get(val);
        }
        
        indexSet.add(dataArr.size() - 1);
        return result;
    }
    
    /** Removes a value from the collection. Returns true if the collection contained the specified element. */
    public boolean remove(int val) {
        Set<Integer> indexSet = indexMap.get(val);
        if (indexSet == null) {
            return false;
        }
        
        int lastVal = dataArr.get(dataArr.size() - 1);
        Set<Integer> lastValSet = indexMap.get(lastVal);
        if (lastVal != val) {
            int valPos = indexSet.iterator().next();
            dataArr.set(valPos, lastVal);
            indexSet.remove(valPos);
            lastValSet.add(valPos);
            if (indexSet.isEmpty()) {
                indexMap.remove(val);
            }
        }

        lastValSet.remove(dataArr.size() - 1);
        if (lastValSet.isEmpty()) {
            indexMap.remove(lastVal);
        }

        dataArr.remove(dataArr.size() - 1);
        return true;
    }
    
    /** Get a random element from the collection. */
    public int getRandom() {
        int next = random.nextInt(dataArr.size());
        return dataArr.get(next);
    }
}

LinkedHashSet: https://docs.oracle.com/javase/9/docs/api/java/util/LinkedHashSet.html

这基本上就是一个HashSet和Doubly linked list的合体,所以在每次操作上,都多了一些overhead所以稍微会比正常的HashSet更慢。但它有俩个好处:1.如果你用iterator遍历它,它输出的顺序会和你插入的顺序是相同的。2. iterator().next()保证了能够O(1)的速率访问到这个Set里面的头节点,这个是HashSet办不到的。其实这里我们也不是真的需要访问头节点,我们只需要访问Set里面任何一个节点即可。可以看得出来,getRandom()和之前那题一摸一样,insert()和remove的差别也就在于我们之前是在HashMap里对一个特定的数值进行操作,而现在我们操作的对象是一个Set。整个思路基本是一致的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值