LeetCode 380. O(1) 时间插入、删除和获取随机元素【哈希表 + 动态数组 | C语言详解】

一、题目描述

实现 RandomizedSet 类:

  • RandomizedSet() 初始化对象

  • insert(val):当元素 val 不存在时插入,并返回 true

  • remove(val):当元素存在时删除,并返回 true

  • getRandom():随机返回集合中的一个元素,每个元素被返回的概率相同

要求:所有操作平均时间复杂度 O(1)。

示例:

复制代码
输入:
["RandomizedSet","insert","remove","insert","getRandom","remove","insert","getRandom"]
[[],[1],[2],[2],[],[1],[2],[]]

输出:
[null,true,false,true,2,true,false,2]

二、题目分析

题目要求:

操作 时间复杂度
insert O(1)
remove O(1)
getRandom O(1)

普通数据结构:

数据结构 问题
数组 删除 O(n)
哈希表 无法随机访问

因此需要 结合两种结构:

复制代码
动态数组 + 哈希表

三、核心数据结构

我们维护两个结构:

1️⃣ 动态数组 nums

存储所有元素

复制代码
nums = [1,2,3]

用于:

复制代码
O(1) 随机访问

2️⃣ 哈希表 map

存储:

复制代码
value -> index

例如:

复制代码
nums: [1,2,3]

map:
1 -> 0
2 -> 1
3 -> 2

这样可以 O(1) 找到元素位置。


四、操作实现

1 插入 insert

步骤:

复制代码
1 判断 val 是否存在
2 若存在 return false
3 nums尾部插入
4 map[val] = index

示例:

复制代码
插入 4

nums: [1,2,3,4]

map:
1->0
2->1
3->2
4->3

时间复杂度:

复制代码
O(1)

五、删除操作(核心)

删除是本题 最关键技巧。

如果直接删除:

复制代码
nums = [1,2,3,4]
删除 2

数组需要移动:

复制代码
[1,3,4]

时间复杂度变成:

复制代码
O(n)

因此我们使用 覆盖删除法:

删除步骤

复制代码
1 找到 val 的 index
2 取最后一个元素 last
3 nums[index] = last
4 更新 map[last]
5 删除最后元素

示例:

复制代码
nums = [1,2,3,4]

删除 2

步骤:

复制代码
index = 1
last = 4

nums[1] = 4

结果:

复制代码
nums = [1,4,3]

map:
1->0
4->1
3->2

时间复杂度:

复制代码
O(1)

六、随机获取元素

由于数组支持随机访问:

复制代码
rand() % size

即可随机返回。

示例:

复制代码
nums = [1,4,3]

rand()%3 -> 0~2

七、完整 C 语言实现

复制代码
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>

#define SIZE 200003

typedef struct Node {
    int key;
    int val;
    struct Node* next;
} Node;

typedef struct {
    int* nums;
    int size;
    int capacity;
    Node* hash[SIZE];
} RandomizedSet;

int hashFunc(int key) {
    long long k = key;
    if (k < 0) k = -k;
    return k % SIZE;
}

Node* findNode(Node* head, int key) {
    while (head) {
        if (head->key == key)
            return head;
        head = head->next;
    }
    return NULL;
}

RandomizedSet* randomizedSetCreate() {

    RandomizedSet* obj = malloc(sizeof(RandomizedSet));

    obj->capacity = 200000;
    obj->size = 0;
    obj->nums = malloc(sizeof(int) * obj->capacity);

    for (int i = 0; i < SIZE; i++)
        obj->hash[i] = NULL;

    srand(time(NULL));

    return obj;
}

bool randomizedSetInsert(RandomizedSet* obj, int val) {

    int h = hashFunc(val);

    if (findNode(obj->hash[h], val))
        return false;

    Node* node = malloc(sizeof(Node));
    node->key = val;
    node->val = obj->size;
    node->next = obj->hash[h];
    obj->hash[h] = node;

    obj->nums[obj->size++] = val;

    return true;
}

bool randomizedSetRemove(RandomizedSet* obj, int val) {

    int h = hashFunc(val);

    Node* cur = obj->hash[h];
    Node* prev = NULL;

    while (cur && cur->key != val) {
        prev = cur;
        cur = cur->next;
    }

    if (!cur)
        return false;

    int index = cur->val;
    int last = obj->nums[obj->size - 1];

    obj->nums[index] = last;

    int h2 = hashFunc(last);
    Node* node = findNode(obj->hash[h2], last);
    node->val = index;

    obj->size--;

    if (prev)
        prev->next = cur->next;
    else
        obj->hash[h] = cur->next;

    free(cur);

    return true;
}

int randomizedSetGetRandom(RandomizedSet* obj) {
    int r = rand() % obj->size;
    return obj->nums[r];
}

void randomizedSetFree(RandomizedSet* obj) {

    for (int i = 0; i < SIZE; i++) {
        Node* cur = obj->hash[i];
        while (cur) {
            Node* tmp = cur;
            cur = cur->next;
            free(tmp);
        }
    }

    free(obj->nums);
    free(obj);
}

八、复杂度分析

操作 时间复杂度
insert O(1)
remove O(1)
getRandom O(1)

空间复杂度:

复制代码
O(n)

九、总结

本题核心思想:

复制代码
动态数组 + 哈希表

关键技巧:

复制代码
删除元素时
用最后一个元素覆盖被删除元素

这样可以避免数组移动,从而保证:

复制代码
删除操作 O(1)
相关推荐
程序喵大人5 小时前
【C++入门】内存模型与资源管理 - 09 shared_ptr 和 weak_ptr 解决共享,也带来代价
开发语言·c++·资源管理·智能指针·内存模型
秋天的怀念_David6 小时前
第二十二夜 · 数字迷雾
java·程序人生·算法
Pniubi6 小时前
力扣283移动零(双指针法)
算法·leetcode·职场和发展
hhb_6186 小时前
大模型落地三重优化实战
人工智能·算法·机器学习
云和数据.ChenGuang6 小时前
光哥说Python RAG 系统需要哪些评估指标:从检索到生成的全链路评测指南
网络·人工智能·python·算法·langchain
jimy17 小时前
C++ 中的 `T&&`:右值引用与转发引用
开发语言·c++
2601_970703838 小时前
工业除湿机租赁:把一份租金合同拆开看
大数据·数据结构·算法·数据库架构·推荐算法
心中有你02148 小时前
Java实现Dijkstra最短路径算法(邻接表 + 优先队列)
java·数据库·算法
ZhengEnCi9 小时前
同样的模型,为什么本地和云 API 差别那么大:量化精度、聊天模板与采样参数的三大元凶
人工智能·算法·llm
Zwawa9 小时前
串口为什么会丢字节:从轮询到中断和环形缓冲区
c语言·单片机·嵌入式