LeetCode 432 全O(1)数据结构

今天捣鼓一天总算是把整个代码给搞通顺了。整体思路是双端链表+Hash的方式,实现代码为C++;

首先定义双端链表结点的数据结构(与LRU那道题有点类似),我们规定,链表是一个升序链表

struct Node {
    Node() {}

    Node(string key) : key(std::move(key)), count(1) {}
    
	// 保存当前的key和count值,其中count默认赋值为1
    string key;
    int count;
	
    Node *pre = nullptr, *next = nullptr;
	// 定义count的自增和自减操作
    void inc() { ++count; }

    void dec() { --count; }
};

接下来首先看需要用到的数据成员,这里使用unordered_map而不是map,因为map底层是红黑树实现,复杂度为O(logn),不满足我们的要求,而unordered_map底层是hash实现,可以满足:

// 保存key到Node*的映射
unordered_map<string, Node *> key_map;
// 保存count到Node*的映射
unordered_map<int, Node *> count_to_node;
// 用于构成双端链表
Node *head, *tail;

注意这里可以不用max和min表示最大和最小,我们规定:

  1. head结点的下一个结点count最小
  2. tail结点的上一个结点count最大

给出了这个定义后,那么getMaxKey()getMinKey()这两个函数就可以先写出来,并且保证都是O(1)的操作:

string getMaxKey() {
	return tail->pre == head ? "" : tail->pre->key;
}

string getMinKey() {
	return head->next == tail ? "" : head->next->key;
}

接下来我们还需要补充几个有关链表的辅助函数,包括链表结点的删除与插入,函数比较简单,这里就不多做讲解,主要有一点,插入时为了防止出现环,需要在插入时对结点进行检查,如果相同则不需要进行插入:

void delete_node(Node *node) {
    // 删除结点
    if (node->pre) node->pre->next = node->next;
    if (node->next) node->next->pre = node->pre;
}

void insert_before(Node *node, Node *insert) {
    insert_after(node, insert->pre);
}

void insert_after(Node *node, Node *insert) {
    if (node == insert) return;
    delete_node(node);
    Node *next = insert->next;
    node->pre = insert;
    node->next = next;
    next->pre = node;
    insert->next = node;
}

由于两个map key类型不同,值类型相同,我们还定义一个函数用于判断map中是否存在键:

template<class T>
bool find_node(const unordered_map<T, Node *> &map, const T &key) {
    return map.find(key) != map.end();
}

然后是构造函数部分,也比较简单,head和tail的count值主要保证他们一定在常规的count范围之外:

AllOne() {
    head = new Node;
    head->count = -1;
    tail = new Node;
    tail->count = INT_MAX - 1;
    head->next = tail;
    tail->pre = head;
}

简单的热身到这里结束,接下来进入重头戏,也就是inc()dec()两个主要的函数:

在此之前,我们再继续加上一条规定,所有count_to_node中保存的Node,都是最新插入的Node值,并且该值一定位于一段具有相等的count的连续Node的第一个结点,这里画个图表示:

image-20210711215623899

因此,我们每进行一次结点的插入时,都需要count_to_node[node0->count] = node0

接下来看inc()部分的代码:

inc()代码分两种情况进行,即key不存在与key存在:

首先是存在部分:

node = new Node(key);
// 插入结点,初始count为1
key_map.insert({key, node});
/*
这里主要是保证新插入的结点是一段连续相等count结点的第一个(像前面解释的)
*/
if (find_node(count_to_node, 1)) {
    // 存在为1的结点,在pre_nodes中插入为1的结点
    insert_before(node, count_to_node[1]);
} else {
    // 有结点但不存在count为1的结点,或者不存在结点,在head之后插入结点
    insert_after(node, head);
}
// 将1更新为最新插入的结点
count_to_node[1] = node;

然后是key不存在的部分:

// 存在结点
node = key_map[key];
int count = node->count;
// 增加前的处理,判断是否最新插入的结点
check_before(node);
// 自增
node->inc();
// 增加后的处理
insert_node(node, node->count);

其中check_before函数主要用于检测当前结点是否是最新插入的结点:

void check_before(const Node *node) {
    int count = node->count;
    if (count_to_node[count] == node) {
        if (node->next->count == count) {
            // 存在下一个结点的count与当前结点相等,节点不唯一,用下一个结点替换当前结点
            count_to_node[count] = node->next;
        } else {
            // 不存在下一个结点的count与当前结点相等
            // 说明当前count的结点唯一,可以删除
            count_to_node.erase(count);
        }
    }
}

增加后的处理函数如下:

void insert_node(Node *node, int count) {
    // 判断count自增后,count_to_node是否存在相关结点
    if (find_node(count_to_node, count)) {
        // 存在结点,在这个结点之前进行插入
        insert_before(node, count_to_node[count]);
        // 当前结点变为最先插入的结点
        count_to_node[count] = node;
    } else {
        // 没有找到
        count_to_node[count] = node;
        if (tail->pre->count < count) {
            // 出现大于count最大值的情况就在尾结点前插入
            insert_before(node, tail);
        }
    }
}

注意这个函数中,我们在没有找到节点时,仅当count大于最大值的时候才进行插入操作,这是一个不太好理解的点。

由于这里是自增,且链表升序,如果在count_to_node中没有找到对应结点,有两种情况(1)说明node的next结点,其count值要比自增后的count还要大,因此不用变动链表的结构;(2)说明当前结点的count值已经是最大,需要更新链表。

到这里,inc()部分就算完成了,其实dec()也差不多,只是多了一种特殊的判断,那就是删除结点的情况,这里直接上完整的dec()代码:

void dec(string key) {
    if (!find_node(key_map, key)) {
        // 不在key_map中,直接退出
        return;
    } else {
        Node *node = key_map[key];
        // 自减前
        check_before(node);
        node->dec();
        // 为0直接删除
        if (node->count == 0) {
            delete_node(node);
            key_map.erase(node->key);
            delete node;
        } else {
            // 不为0,执行自减后的结点插入
            insert_node(node, node->count);
        }
    }
}

整体代码如下:

#include <string>
#include <unordered_map>

using namespace std;

struct Node {
    Node() {}

    Node(string key) : key(std::move(key)), count(1) {}

    string key;
    int count;

    Node *pre = nullptr, *next = nullptr;

    void inc() { ++count; }

    void dec() { --count; }
};

class AllOne {
public:
    /** Initialize your data structure here. */
    AllOne() {
        head = new Node;
        head->count = -1;
        tail = new Node;
        tail->count = INT_MAX - 1;
        head->next = tail;
        tail->pre = head;
    }

    /** Inserts a new key <Key> with value 1. Or increments an existing key by 1. */
    void inc(string key) {
        Node *node;
        if (!find_node(key_map, key)) {
            node = new Node(key);
            key_map.insert({key, node});
            if (find_node(count_to_node, 1)) {
                insert_before(node, count_to_node[1]);
            } else {
                insert_after(node, head);
            }
            count_to_node[1] = node;
        } else {
            node = key_map[key];
            check_before(node);
            node->inc();
            insert_node(node, node->count);
        }
    }

    void dec(string key) {
        if (!find_node(key_map, key)) {
            return;
        } else {
            Node *node = key_map[key];
            check_before(node);
            node->dec();
            if (node->count == 0) {
                delete_node(node);
                key_map.erase(node->key);
                delete node;
            } else {
                insert_node(node, node->count);
            }
        }
    }
    
    string getMaxKey() {
        return tail->pre == head ? "" : tail->pre->key;
    }

    string getMinKey() {
        return head->next == tail ? "" : head->next->key;
    }

private:
    void delete_node(Node *node) {
        if (node->pre) node->pre->next = node->next;
        if (node->next) node->next->pre = node->pre;
    }

    void check_before(const Node *node) {
        int count = node->count;
        if (count_to_node[count] == node) {
            if (node->next->count == count) {
                count_to_node[count] = node->next;
            } else {
                count_to_node.erase(count);
            }
        }
    }

    void insert_before(Node *node, Node *insert) {
        insert_after(node, insert->pre);
    }

    void insert_after(Node *node, Node *insert) {
        if (node == insert) return;
        delete_node(node);
        Node *next = insert->next;
        node->pre = insert;
        node->next = next;
        next->pre = node;
        insert->next = node;
    }

    void insert_node(Node *node, int count) {
        if (find_node(count_to_node, count)) {
            insert_before(node, count_to_node[count]);
            count_to_node[count] = node;
        } else {
            count_to_node[count] = node;
            if (tail->pre->count < count) {
                insert_before(node, tail);
            }
        }
    }

    template<class T>
    bool find_node(const unordered_map<T, Node *> &map, const T &key) {
        return map.find(key) != map.end();
    }

    unordered_map<string, Node *> key_map;
    unordered_map<int, Node *> count_to_node;
    Node *head, *tail;
};

这道题确实有一定难度,做了很久,但是做出来了还是很有成就感的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值