⭐算法入门⭐《哈希表》中等03 —— LeetCode 380. O(1) 时间插入、删除和获取随机元素

饭不食,水不饮,题必须刷

C语言免费动漫教程,和我一起打卡!
光天化日学C语言

LeetCode 太难?先看简单题!
C语言入门100例

数据结构难?不存在的!
画解数据结构

LeetCode 太简单?算法学起来!
夜深人静写算法

文章目录

  • 一、题目
    • 1、题目描述
    • 2、基础框架
    • 3、原题链接
  • 二、解题报告
    • 1、思路分析
    • 2、时间复杂度
    • 3、代码详解
  • 三、本题小知识

一、题目

1、题目描述

  设计一个支持在平均 时间复杂度 O ( 1 ) O(1) O(1) 下,执行以下操作的数据结构。
insert(val):当元素 v a l val val 不存在时,向集合中插入该项。
remove(val):元素 v a l val val 存在时,从集合中移除该项。
getRandom:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。
  样例输入: ["RandomizedSet","insert","remove","insert","getRandom","remove","insert","getRandom"] [[],[1],[2],[2],[],[1],[2],[]]
  样例输出: [null,true,false,true,2,true,false,2]

2、基础框架

  • C语言版本 给出的基础框架代码如下:
typedef struct {

} RandomizedSet;

/** Initialize your data structure here. */

RandomizedSet* randomizedSetCreate() {

}

/** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
bool randomizedSetInsert(RandomizedSet* obj, int val) {

}

/** Removes a value from the set. Returns true if the set contained the specified element. */
bool randomizedSetRemove(RandomizedSet* obj, int val) {

}

/** Get a random element from the set. */
int randomizedSetGetRandom(RandomizedSet* obj) {

}

void randomizedSetFree(RandomizedSet* obj) {

}

/**
 * Your RandomizedSet struct will be instantiated and called as such:
 * RandomizedSet* obj = randomizedSetCreate();
 * bool param_1 = randomizedSetInsert(obj, val);
 
 * bool param_2 = randomizedSetRemove(obj, val);
 
 * int param_3 = randomizedSetGetRandom(obj);
 
 * randomizedSetFree(obj);
*/

3、原题链接

( 1 ) (1) (1) 380. O(1) 时间插入、删除和获取随机元素
( 2 ) (2) (2) 剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器

二、解题报告

1、思路分析

  1)维护一个哈希表和两个映射数组,这两个数组分别是 哈希地址到数组下标 的映射,和 数组下标到哈希地址 的映射;
  2)插入操作:对哈希表执行查找,如果没有找到,则执行插入,插入过程中更新两个映射数组;
  3)删除操作:对哈希表执行查找,如果找到,则执行删除,删除过程中对映射数组执行和最后一个元素进行交换,保证删除是 O ( 1 ) O(1) O(1) 的;
  4)随机获取:对 数组下标到哈希地址 这个映射数组进行随机,得到一个哈希地址,再去哈希表中直接通过下标索引找到关键字后返回即可;

2、时间复杂度

  • 哈希表的插入查找均摊复杂度为 O ( 1 ) O(1) O(1),总共 n n n 个数字的枚举,时间复杂度为 O ( n ) O(n) O(n)

3、代码详解

/******************** 哈希表 开放定址法 ********************/
#define maxn (1<<17)
#define mask (maxn-1)
#define DataType int
#define Boolean int
#define NULLKEY (1<<30)    /* 空槽标记不能用-1,会导致正常值也为-1的情况*/

typedef struct {
    DataType data[maxn];
}HashTable;

void HashInit(HashTable *ht) {
    int i;
    for(i = 0; i < maxn; ++i) {
        ht->data[i] = NULLKEY;
    }
}

int HashGetAddr(DataType key) {
    return key & mask;        // 除留余数法
}

Boolean HashSearchKey(HashTable *ht, DataType key, int *addr) {
    int startaddr = HashGetAddr(key);
    *addr = startaddr;
    while(ht->data[*addr] != key) {
        *addr = HashGetAddr(*addr + 1);
        if(ht->data[*addr] == NULLKEY) {
            return 0;                    // 遇到了空槽,说明没找到,返回 0
        }
        if(*addr == startaddr) {
            return 0;                    // 找了一圈都没找到,循环了
        }
    }
    return 1;
}

int HashInsert(HashTable *ht, DataType key) {
    int addr = HashGetAddr(key);
    int retaddr;
    if ( HashSearchKey(ht, key, &retaddr ) ) {
        return retaddr;
    } 
    while(ht->data[addr] != NULLKEY)
        addr = HashGetAddr(addr + 1);
    ht->data[addr] = key;
    return addr;
}

int HashRemove(HashTable *ht, DataType key) {
    int addr;
    if ( !HashSearchKey(ht, key, &addr ) ) {
        return NULLKEY;
    } 
    ht->data[addr] = NULLKEY;
    return addr;
}

/******************** 哈希表 开放定址法 ********************/

typedef struct {
    HashTable ht;
    int addr2pos[maxn];                       // (1)
    int pos2addr[maxn];                       // (2)
    int pos;
} RandomizedSet;

/** Initialize your data structure here. */

RandomizedSet* randomizedSetCreate() {
    RandomizedSet *rs = (RandomizedSet *)malloc( sizeof(RandomizedSet) );
    HashInit( &rs->ht );
    rs->pos = 0;                              // (3)
    return rs;
}

/** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
bool randomizedSetInsert(RandomizedSet* obj, int val) {
    int addr;
    if (!HashSearchKey(&obj->ht, val, &addr)) {
        addr = HashInsert(&obj->ht, val);
        obj->addr2pos[ addr ] = obj->pos;         // (4)
        obj->pos2addr[ obj->pos++ ] = addr;       // (5)
        return true;
    }
    return false;
}

void swap(int *a, int *b) {
    int tmp = *a; 
    *a = *b;
    *b = tmp;
}

/** Removes a value from the set. Returns true if the set contained the specified element. */
bool randomizedSetRemove(RandomizedSet* obj, int val) {
    int addr, pre;
    if (HashSearchKey(&obj->ht, val, &addr)) {
        addr = HashRemove( &obj->ht, val );
        pre = obj->addr2pos[ addr ];                               // (6)
        obj->addr2pos[ obj->pos2addr[obj->pos-1] ] = pre;          // (7)
        swap( &obj->pos2addr[pre], &obj->pos2addr[obj->pos-1] );   // (8)
        -- obj->pos;
        return true;
    }
    return false;
}

/** Get a random element from the set. */
int randomizedSetGetRandom(RandomizedSet* obj) {
    return  obj->ht.data[ obj->pos2addr[ rand() % obj->pos ] ];
}

void randomizedSetFree(RandomizedSet* obj) {
    free(obj);
}

/**
 * Your RandomizedSet struct will be instantiated and called as such:
 * RandomizedSet* obj = randomizedSetCreate();
 * bool param_1 = randomizedSetInsert(obj, val);
 
 * bool param_2 = randomizedSetRemove(obj, val);
 
 * int param_3 = randomizedSetGetRandom(obj);
 
 * randomizedSetFree(obj);
*/
  • ( 1 ) (1) (1) 哈希表地址到数组下标的映射;
  • ( 2 ) (2) (2) 数组下标到哈希表地址的映射;
  • ( 3 ) (3) (3) 初始化数组长度为 0;
  • ( 4 ) − ( 5 ) (4) - (5) (4)(5) 插入数据的过程中,同时记录 哈希表地址 和 数组下标的映射关系;
  • ( 6 ) − ( 8 ) (6) - (8) (6)(8) 将数组中需要删除的那个元素,和数组最后一个元素进行交换,这样可以做到删除的时候是 O ( 1 ) O(1) O(1) 的;

三、本题小知识

  在一个数组中删除数据,可以将它和最后一个数组元素进行交换,然后再将数组的size减一,这样可以做到 O ( 1 ) O(1) O(1),只不过这样做会改变原有数组顺序,如果对顺序要求不大就可以这么做。


你可能感兴趣的:(《LeetCode算法全集》,数据结构,算法,leetcode,哈希表,C语言)