这是LeetCode上的 [030,插入、删除和随机访问都是 O(1) 的容器],难度为 [中等]

题目

设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构:

  • insert(val):当元素 val 不存在时返回 true ,并向集合中插入该项,否则返回 false 。
  • remove(val):当元素 val 存在时返回 true ,并从集合中移除该项,否则返回 false 。
  • getRandom:随机返回现有集合中的一项。每个元素应该有 相同的概率 被返回。

题解

思路分析

根据题意的前两个条件,插入和删除要求时间复杂度为O(1),很容易想到用哈希表,哈希表的插入,删除,查找一个元素都只需要O(1)的时间,题意的最后一个条件随机返回一个数据,且要求返回的每个数据概率都相等,显然哈希表不满足这个条件,下面通过在java中实现哈希表的HashMap为例来说明

底层采用的是数组+链表+红黑树,数组的每一个元素除存放元素外,还有一个引用变量用于指向下一个元素(链表)。它查找元素是通过元素的哈希值求余数(散列函数)的方式确定元素在数组的位置,数组的位置可能存在一个链表来存放其他元素(哈希值相同),故不能等概率返回其中的每一个数值。

等概率的返回每个元素,显然数组满足这个条件,假设数组长度为n,则每个元素被返回的概率为1/n

综上,我们应该设计一个包含哈希表,数组的的容器

由于需要等概率的返回每个元素,故数据只能存放在数组中,可以使用java的动态数组ArrayList,则它满足第1个和第3个条件,但他不满足第2个条件,例如删除元素它需要做下面两个操作

  • 遍历数组找到要删除的元素,当删除最后一个元素时,需要遍历完数组,时间复杂度为O(n),当删除第一个元素时,为O(1)
  • 当找到要删除的元素时,如果删除的是最后一个元素,时间复杂度为O(1),当删除第一个元素时,需要把第一个元素的后面所有元素前移,时间复杂度为O(n)

综上,数组的删除元素的时间复杂度为O(n),因此我们需要借助HashMap来解决上面两个问题

解决第一个问题

使用HashMap的键来存放元素,值来存放元素在数组的索引,因此当删除元素时,不需要遍历数组找到删除元素,通过map键即可获取删除元素的索引,时间复杂度为O(1)

解决第二个问题

当在数组找到删除元素的位置时,我们只需把它的值体换成数组的最后一个元素的值,然后删除最后一个元素,而数组删除最后一个元素的时间复杂度为O(1),不需要移动元素。由于数组的最后一个元素的值赋给了删除元素的位置,因此需要改变HashMap的最后一个元素的索引

代码实现

public class RandomizedSet {

    private HashMap<Integer, Integer> numToLocation;
    private ArrayList<Integer> nums;
    Random random = new Random();

    public RandomizedSet() {
        numToLocation = new HashMap<>();
        nums = new ArrayList<>();
    }

    public boolean insert(Integer val) {
        // 通过map判断插入的元素是否存在与数组
        if (numToLocation.containsKey(val)){
            return false;
        }
        // 不存在执行逻辑
        // 记录插入元素在数组中的位置,每次插入都是在数组尾部插入,故索引为数组的长度
        numToLocation.put(val, nums.size());
        // 向数组尾部添加元素
        nums.add(val);
        return true;
    }

    public boolean remove(Integer val) {
        // 通过map判断删除的元素是否存在与数组中
        if (!numToLocation.containsKey(val)) {
            return false;
        }
        // 存在执行逻辑
        // 通过map获取删除元素在数组的索引
        Integer location = numToLocation.get(val);
        // 获取数组最后一个元素
        Integer lastNum = nums.get(nums.size() - 1);
        // 把数组中要删除的元素的值替换成数组的最后一个元素
        nums.set(location, lastNum);
        // 删除数组最后一个元素
        nums.remove(nums.size() - 1);
        // 通过map把数组最后一个元素的索引改成删除元素的索引
        numToLocation.put(lastNum, location);
        // 把要删除元素的索引从map中删除
        numToLocation.remove(val);
        return true;
    }

    public Integer getRandom() {
        // 假设数组长度为n,则生成[0 ~ n-1]之间的随机数
        int i = random.nextInt(nums.size());
        return nums.get(i);
    }
}

在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐