LeetCode //C - 432. All O`one Data Structure

432. All O`one Data Structure

Design a data structure to store the strings’ count with the ability to return the strings with minimum and maximum counts.

Implement the AllOne class:

  • AllOne() Initializes the object of the data structure.
  • inc(String key) Increments the count of the string key by 1. If key does not exist in the data structure, insert it with count 1.
  • dec(String key) Decrements the count of the string key by 1. If the count of key is 0 after the decrement, remove it from the data structure. It is guaranteed that key exists in the data structure before the decrement.
  • getMaxKey() Returns one of the keys with the maximal count. If no element exists, return an empty string “”.
  • getMinKey() Returns one of the keys with the minimum count. If no element exists, return an empty string “”.

Note that each function must run in O(1) average time complexity.
 

Example 1:

Input:
[“AllOne”, “inc”, “inc”, “getMaxKey”, “getMinKey”, “inc”, “getMaxKey”, “getMinKey”]
[[], [“hello”], [“hello”], [], [], [“leet”], [], []]
Output:
[null, null, null, “hello”, “hello”, null, “hello”, “leet”]
Explanation
AllOne allOne = new AllOne();
allOne.inc(“hello”);
allOne.inc(“hello”);
allOne.getMaxKey(); // return “hello”
allOne.getMinKey(); // return “hello”
allOne.inc(“leet”);
allOne.getMaxKey(); // return “hello”
allOne.getMinKey(); // return “leet”

Constraints:
  • 1 <= key.length <= 10
  • key consists of lowercase English letters.
  • It is guaranteed that for each call to dec, key is existing in the data structure.
  • At most 5 ∗ 1 0 4 5 * 10^4 5104 calls will be made to inc, dec, getMaxKey, and getMinKey.

From: LeetCode
Link: 432. All O`one Data Structure


Solution:

Ideas:

1. Efficiently Managing Counts with a Linked List:

  • The linked list allows constant-time insertion and deletion of nodes with specific counts. Nodes are always ordered by count, so head->next gives the minimum count, and tail->prev gives the maximum count.

2. Using Hash Tables for Fast Lookup:

  • Hash tables (data and keys) allow fast lookups for O(1) access to nodes and strings.
  • data provides access to each key’s count and associated Node, while keys in each Node provides direct access to strings within that count.

3. Handling Edge Cases with Sentinel Nodes:

  • Sentinel nodes (head and tail) simplify boundary conditions. There’s no need to check if we’re at the start or end of the list when updating nodes for incrementing or decrementing counts.

4. Memory Management and Avoiding Dangling Pointers:

  • Temporary pointers (nodeToRemove and nextNode) are used before deleting nodes to avoid heap-use-after-free errors by ensuring that no dangling references remain after node deletion.
Code:
typedef struct Node {
    
    
    int count;
    struct Node *prev, *next;
    struct KeyNode *keys;
} Node;

typedef struct KeyNode {
    
    
    char *key;
    UT_hash_handle hh;
} KeyNode;

typedef struct DataEntry {
    
    
    char *key;
    int count;
    Node *node;
    UT_hash_handle hh;
} DataEntry;

typedef struct {
    
    
    Node *head, *tail;
    DataEntry *data;
} AllOne;

Node* newNode(int count) {
    
    
    Node *node = (Node*)malloc(sizeof(Node));
    node->count = count;
    node->prev = node->next = NULL;
    node->keys = NULL;
    return node;
}

void addKey(Node *node, char *key) {
    
    
    KeyNode *keyNode = (KeyNode*)malloc(sizeof(KeyNode));
    keyNode->key = key;
    HASH_ADD_KEYPTR(hh, node->keys, key, strlen(key), keyNode);
}

void removeKey(Node *node, char *key) {
    
    
    KeyNode *keyNode;
    HASH_FIND_STR(node->keys, key, keyNode);
    if (keyNode) {
    
    
        HASH_DEL(node->keys, keyNode);
        free(keyNode);
    }
}

AllOne* allOneCreate() {
    
    
    AllOne *obj = (AllOne*)malloc(sizeof(AllOne));
    obj->head = newNode(INT_MIN);
    obj->tail = newNode(INT_MAX);
    obj->head->next = obj->tail;
    obj->tail->prev = obj->head;
    obj->data = NULL;
    return obj;
}

Node* insertNodeAfter(Node *node, int count) {
    
    
    Node *newNode = (Node*)malloc(sizeof(Node));
    newNode->count = count;
    newNode->keys = NULL;
    newNode->prev = node;
    newNode->next = node->next;
    node->next->prev = newNode;
    node->next = newNode;
    return newNode;
}

void removeNode(Node *node) {
    
    
    node->prev->next = node->next;
    node->next->prev = node->prev;
    HASH_CLEAR(hh, node->keys);
    free(node);
}

void allOneInc(AllOne* obj, char* key) {
    
    
    DataEntry *entry;
    HASH_FIND_STR(obj->data, key, entry);

    if (!entry) {
    
     // Key not found, add new entry with count 1
        entry = (DataEntry*)malloc(sizeof(DataEntry));
        entry->key = strdup(key);
        entry->count = 1;
        if (obj->head->next->count != 1) {
    
    
            entry->node = insertNodeAfter(obj->head, 1);
        } else {
    
    
            entry->node = obj->head->next;
        }
        HASH_ADD_KEYPTR(hh, obj->data, entry->key, strlen(entry->key), entry);
        addKey(entry->node, entry->key);
    } else {
    
     // Key exists, increment its count
        Node *node = entry->node;
        if (node->next->count != entry->count + 1) {
    
    
            node = insertNodeAfter(node, entry->count + 1);
        } else {
    
    
            node = node->next;
        }
        addKey(node, key);
        entry->node = node;
        entry->count++;
        removeKey(entry->node->prev, key);
        if (entry->node->prev->keys == NULL && entry->node->prev != obj->head) {
    
    
            Node *nodeToRemove = entry->node->prev;
            entry->node->prev = NULL; // Avoid dangling reference in entry
            removeNode(nodeToRemove);
        }
    }
}

void allOneDec(AllOne* obj, char* key) {
    
    
    DataEntry *entry;
    HASH_FIND_STR(obj->data, key, entry);

    if (!entry) return; // Check if entry is NULL to avoid undefined behavior

    if (entry->count == 1) {
    
     // Remove entry if count goes to 0
        Node *nodeToRemove = entry->node; // Temporary reference
        removeKey(nodeToRemove, key);
        HASH_DEL(obj->data, entry); // Remove entry from hash table
        free(entry->key);           // Free the key memory
        free(entry);                // Free the entry itself
        if (nodeToRemove->keys == NULL && nodeToRemove != obj->head && nodeToRemove != obj->tail) {
    
    
            removeNode(nodeToRemove); // Remove the node if no keys remain
        }
    } else {
    
     // Decrement count
        Node *node = entry->node;
        if (node->prev->count != entry->count - 1) {
    
    
            node = insertNodeAfter(node->prev, entry->count - 1);
        } else {
    
    
            node = node->prev;
        }
        addKey(node, key);
        entry->node = node;
        entry->count--;
        Node *nextNode = entry->node->next; // Temporary pointer for next node
        removeKey(nextNode, key);
        if (nextNode->keys == NULL && nextNode != obj->tail) {
    
    
            removeNode(nextNode);
        }
    }
}

char* allOneGetMaxKey(AllOne* obj) {
    
    
    if (obj->tail->prev->keys)
        return obj->tail->prev->keys->key;
    return "";
}

char* allOneGetMinKey(AllOne* obj) {
    
    
    if (obj->head->next->keys)
        return obj->head->next->keys->key;
    return "";
}

void allOneFree(AllOne* obj) {
    
    
    Node *node = obj->head;
    while (node) {
    
    
        Node *next = node->next;
        HASH_CLEAR(hh, node->keys);
        free(node);
        node = next;
    }
    DataEntry *entry, *tmp;
    HASH_ITER(hh, obj->data, entry, tmp) {
    
    
        HASH_DEL(obj->data, entry);
        free(entry->key);
        free(entry);
    }
    free(obj);
}

猜你喜欢

转载自blog.csdn.net/navicheung/article/details/143354338