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)
相关推荐
sycmancia1 小时前
C++——类模板的概念和意义
c++
xiaoye-duck1 小时前
《算法题讲解指南:递归,搜索与回溯算法--二叉树中的深搜》--8.二叉树剪枝,9.验证二叉搜索树
c++·算法·深度优先·递归
承渊政道2 小时前
C++学习之旅【异常相关内容以及类型转换介绍】
c语言·c++·笔记·vscode·学习·macos·visual studio
liulilittle2 小时前
Debian/Ubuntu 18.04 上安装 GLIBC 2.28 (2026)
linux·运维·服务器·开发语言·c++·ubuntu·debian
承渊政道2 小时前
C++学习之旅【深入回溯C++11的发展历程】
c语言·c++·笔记·vscode·学习·macos·visual studio
像素猎人3 小时前
数据结构之——图论中常用的方向数组是如何定义的
算法
爱编码的小八嘎10 小时前
C语言完美演绎3-15
c语言
卷福同学10 小时前
QClaw内测体验,能用微信指挥AI干活了
人工智能·算法·ai编程
sali-tec10 小时前
C# 基于OpenCv的视觉工作流-章34-投影向量
图像处理·人工智能·opencv·算法·计算机视觉