# 2022q1 Homework1 (quiz1) contributed by < [`yaohwang99`](https://github.com/yaohwang99) > > [quiz1](https://hackmd.io/@sysprog/linux2022-quiz1) > [graphviz tutorial](https://www.tonyballantyne.com/graphs.html) ## 1. Two Sum ### Class note In [class note](https://hackmd.io/5zdyXn6uQMOeSoVBapuVNw?view#Uduru0522), Uduru0522 showed that it is more efficient to delete a node if we use indirect pointer `pprev`. Consider the version with the following data structure: ```c struct hlist_head { struct hlist_node *first; }; struct hlist_node { struct hlist_node *next, *pprev; }; ``` ```graphviz digraph G { rankdir = LR; node[shape = "record"] list_head[label = "<m>hlist_head | <n>first"] node_1[label = "<m>hlist_node | {<p>prev | <n>next}", group = list]; node_2[label = "<m>hlist_node | {<p>prev | <n>next}", group = list]; node_3[label = "<m>hlist_node | {<p>prev | <n>next}", group = list]; NULL_1, NULL_2[shape = plaintext, label = "NULL", group = list] {rank = "min" list_head NULL_1} list_head -> node_1:m; node_1:p -> NULL_1 node_1:n -> node_2:m; node_2:p -> node_1:m; node_2:n -> node_3:m; node_3:p -> node_2:m; node_3:n -> NULL_2; } ``` :::info + `prev` of the first node is `NULL` because it can only point to `struct hlist_node`. + `prev` and `next` points to `struct hlist_node`. ::: :::info In the above version, traversing the nodes would be more efficient because it is a direct pointer. However, for a decent has table, collision rarely occurs so the linked list will not be long. Therefore, the advantage of deleting a node outweigh the disadvantage of traversing the nodes. ::: --- ### Analyze `map_add()` ```c= void map_add(map_t *map, int key, void *data) { struct hash_key *kn = find_key(map, key); if (kn) return; kn = malloc(sizeof(struct hash_key)); kn->key = key, kn->data = data; struct hlist_head *h = &map->ht[hash(key, map->bits)]; struct hlist_node *n = &kn->node, *first = h->first; n->next = first;//AAA if (first) first->pprev = &n->next; h->first = n; n->pprev = &h->first;//BBB } ``` ```graphviz digraph G { rankdir = LR; forcelabels=true; node[shape = "record"] map_t[shape = plaintext, label = map, group = list] hash_table[label = "<bits>bits | <ht>ht"] list_head[label = "<h>...|...|{<m>hlist_head.first} | ...|..."]; node_1[xlabel = hlist_node, label = "{<key>key|<data>data} | {<prev>prev | <next>next}", group = list] node_2[xlabel = hlist_node, label = "{<key>key|<data>data} | {<prev>prev | <next>next}", group = list] NULL[shape = plaintext, label = "NULL", group = list] map_t -> hash_table hash_table: ht->list_head:h list_head:m -> node_1 node_1:prev -> list_head[color = gray] node_1:next -> node_2 node_2:prev -> node_1:next[color = gray] node_2:next -> NULL } ``` Above graph is initial state --- ```graphviz digraph G { rankdir = LR; forcelabels=true; node[shape = "record"] map_t[shape = plaintext, label = map] hash_table[label = "<bits>bits | <ht>ht"] list_head[label = "<h>...|...|{<m>hlist_head.first} | ...|..."]; node_1[xlabel = hlist_node, label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node_2[xlabel = hlist_node, label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node_3[label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node[shape = plaintext] map_t -> hash_table hash_table: ht->list_head:h list_head:m -> node_1 node_1:prev -> list_head[color = gray] node_1:next -> node_2 node_2:prev -> node_1:next[color = gray] node_2:next -> NULL kn->node_3 h->list_head n->node_3:prev first->node_1 } ``` ```c=7 kn = malloc(sizeof(struct hash_key)); kn->key = key, kn->data = data; struct hlist_head *h = &map->ht[hash(key, map->bits)]; struct hlist_node *n = &kn->node, *first = h->first; ``` --- ```graphviz digraph G { rankdir = LR; forcelabels=true; node[shape = "record"] map_t[shape = plaintext, label = map] hash_table[label = "<bits>bits | <ht>ht"] list_head[label = "<h>...|...|{<m>hlist_head.first} | ...|..."]; node_1[xlabel = hlist_node, label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node_2[xlabel = hlist_node, label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node_3[label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node[shape = plaintext] map_t -> hash_table hash_table: ht->list_head:h list_head:m -> node_1 node_1:prev -> list_head[color = gray] node_1:next -> node_2 node_2:prev -> node_1:next[color = gray] node_2:next -> NULL kn->node_3 h->list_head n->node_3:prev first->node_1 node_3:next->node_1 } ``` ```c=12 n->next = first;//AAA ``` --- ```graphviz digraph G { rankdir = LR; forcelabels=true; node[shape = "record"] map_t[shape = plaintext, label = map] hash_table[label = "<bits>bits | <ht>ht"] list_head[label = "<h>...|...|{<m>hlist_head.first} | ...|..."]; node_1[xlabel = hlist_node, label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node_2[xlabel = hlist_node, label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node_3[label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node[shape = plaintext] map_t -> hash_table hash_table: ht->list_head:h node_1:next -> node_2 node_2:prev -> node_1:next[color = gray] node_2:next -> NULL kn->node_3 h->list_head n->node_3:prev first->node_1 node_3:next->node_1 list_head:m->node_3 node_1:prev->node_3:next[color = gray] } ``` ```c=13 if (first) first->pprev = &n->next; h->first = n; ``` --- ```graphviz digraph G { rankdir = LR; forcelabels=true; node[shape = "record"] map_t[shape = plaintext, label = map] hash_table[label = "<bits>bits | <ht>ht"] list_head[label = "<h>...|...|{<m>hlist_head.first} | ...|..."]; node_1[xlabel = hlist_node, label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node_2[xlabel = hlist_node, label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node_3[label = "{<key>key|<data>data} | {<prev>prev | <next>next}"] node[shape = plaintext] map_t -> hash_table hash_table: ht->list_head:h node_1:next -> node_2 node_2:prev -> node_1:next[color = gray] node_2:next -> NULL kn->node_3 h->list_head first->node_1 node_3:next->node_1 list_head:m->node_3 node_1:prev->node_3:next[color = gray] node_3:prev->list_head:m[color = gray] n->node_3:prev } ``` ```c=16 n->pprev = &h->first;//BBB ``` --- ### main() 1. Create map pointer to new map 2^10^ bucket 2. Set `*returnSize` to 0 and space for 2 integer. 3. If `nums` does not have an answer, returnSize will still be 0 and return `ret`, implying that `*ret` contains garbage value. 4. Iterate through `nums`, use `targer - nums[i]` as key to find the index of the corresponding number. 5. If found, return the answer. If not found, add `nums[i]` and current index `i` to map, continue to next element of `nums`. ```c int *twoSum(int *nums, int numsSize, int target, int *returnSize) { map_t *map = map_init(10); *returnSize = 0; int *ret = malloc(sizeof(int) * 2); if (!ret) goto bail; for (int i = 0; i < numsSize; i++) { int *p = map_get(map, target - nums[i]); if (p) { /* found */ ret[0] = i, ret[1] = *p; *returnSize = 2; break; } p = malloc(sizeof(int)); *p = i; map_add(map, nums[i], p); } bail: map_deinit(map); return ret; } ``` :::warning In map_deinit(), I don't understand why `n->pprev` can be NULL. [`cy023`](https://hackmd.io/@cy023/linux2022-quiz1#%E9%87%8B%E6%94%BE-Hash-Table-%E4%BD%BF%E7%94%A8%E7%A9%BA%E9%96%93) pointed out that it can pass leetcode test even if it is commented. ```c=13 if (!n->pprev) /* unhashed */ goto bail; ``` ::: ### Read `hash.h` Note: Initialize hash using [designated initializer](https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Designated-Inits.html). ```c=15 #define DEFINE_HASHTABLE(name, bits) \ struct hlist_head name[1 << (bits)] = \ { [0 ... ((1 << (bits)) - 1)] = HLIST_HEAD_INIT } ``` Hash function: Linux kernel will check the word size of the machine and use the corresponding hash function. >If key is 64 bits, use hash_long(), if key is 32 bits, use hash_32(). If word size is 32 bits, hash_long() is hash_32(), if word size is 64 bits, hash_long() is hash_64() If word size is 32 bits but wants to use hash_64(), `hash_32((u32)val ^ __hash_32(val >> 32), bits)` produces the same result. The hash function `val * GOLDEN_RATIO_32 >> (32 - bits)` uses the first `bits` number of bits of `val * GOLDEN_RATIO_32` as bucket index. For example, bits = 3, val = 5, then `val * GOLDEN_RATIO_32` == 0xE8EA9F63 (discard overflowed bits), bucket index = 0xE. ## 2. Remove Duplicates from Sorted List II ### Sample code 1. If `head->val` equals `head->next->value`, move `head` to next node until the statement is false or end of the list. 2. Use `head->next` as new head of the sublist, repeat the algorithm. ```c #include <stddef.h> struct ListNode { int val; struct ListNode *next; }; struct ListNode *deleteDuplicates(struct ListNode *head) { if (!head) return NULL; if (head->next && head->value == head->next->value) { /* Remove all duplicate numbers */ while (head->next && head->value == head->next->value)) head = head->next; return deleteDuplicates(head->next); } head->next = deleteDuplicates(head->next); return head; } ``` :::warning In the sample code, the removed node is not freed and is not pointed by any pointer. ::: --- ### Iterative method Create `struct ListNode *removeSublist(struct ListNode *head)` which removes all the repeated value until the first non-repeated node. ```c struct ListNode *removeSublist(struct ListNode *head){ if (!head) return NULL; while (head->next && head->value == head->next->value)){ if (head->next && head->value == head->next->value) { /* Remove all duplicate numbers */ while (head->next && head->value == head->next->value)) head = head->next; head = head->next; } } return head; } ``` For example, the following list ```graphviz digraph G{ rankdir = "LR" node[shape = circle] 11[label = "1"] 12[label = "1"] 22[label = "2"] 21[label = "2"] 23[label = "2"] 11->12->21->22->23->3->4 } ``` becomes: ```graphviz digraph G{ rankdir = "LR" node[shape = circle] 3->4 } ``` after calling `struct ListNode *removeSublist(struct ListNode *head)`. --- Using `struct ListNode *removeSublist(struct ListNode *head)`, implement the algorithm by iteration. 1. Set `head` to the first non-repeated node by calling `removeSublist()`. 2. Walk through the list with `prev` initialized to `head` 3. Return `head`. ```c #include <stddef.h> struct ListNode { int val; struct ListNode *next; }; struct ListNode *removeSublist(struct ListNode *head){ if (!head) return NULL; while (head->next && head->value == head->next->value)){ if (head->next && head->value == head->next->value) { /* Remove all duplicate numbers */ while (head->next && head->value == head->next->value)) head = head->next; head = head->next; } } return head; } struct ListNode *deleteDuplicates(struct ListNode *head) { if (!head) return NULL; head = removeSublist(head); struct ListNode *prev= head; while (prev) { prev -> next = removeSublist(prev->next); prev = prev->next; } return head; } ``` --- ### Circular doublely-linked list 1. Convert the list to non-circular list by `head->prev->next=NULL` 2. Treat the list as singly-linked list and use head->next as head node for `deleteDuplicates()`, which could be either the recursive or the iterative version. 3. Restore the `prev` pointer in each node, set `next` of last node to head to convert back to circular list. ```c struct d_ListNode { int val; struct list_head *next, *prev; }; struct list_head *d_deleteDuplicates(struct ListNode *head) { if (!head || list_empty(head)) return head; head->prev->next=NULL; head->next = deleteDuplicates(head->next); struct list_head *curr = head; while(curr->next) { curr->next->prev=curr; curr = curr->next; } head->prev = curr; curr->next = head; return head; } ``` ## 3. LRU Cache ### Define data structure 1. `dhead` and `hlink` forms a list by every node in cache. dhead is the head and the tail is the least recently used node. 2. `hhead[]` is each bucket of the hash table, each bucket points to nodes connected by `hlink`. 3. `capacity` is the max number of node the cache can contain. `count` records current number of nodes in the cache. ```c typedef struct { int capacity, count; struct list_head dhead, hheads[]; } LRUCache; typedef struct { int key, value; struct list_head hlink, dlink; } LRUNode; ``` ```graphviz digraph G{ compound = true rankdir = LR node[shape = record] subgraph cluster_0{ label = LRUCache style=filled color=gray90 LRUCache[label = "capacity|count|<dhead>dhead|<hhead0>hheads[0]|<hhead1>hheads[1]|...|<hhead2>hheads[capacity-1]"] } subgraph cluster_1{ label = LRUNode style=filled color=gray90 LRUNode0[label = "{<key>key|<value>value}|<dlink>dlink|<hlink>hlink"] } subgraph cluster_2{ label = LRUNode style=filled color=gray90 LRUNode1[label = "{<key>key|<value>value}|<dlink>dlink|<hlink>hlink"] } NULL[shape = plaintext] NULL2[ label = NULL shape = plaintext] NULL3[label = NULL shape = plaintext] LRUCache:dhead->LRUNode0:dlink->LRUNode1:dlink[dir=both] LRUNode1:dlink->NULL LRUCache:hhead0->LRUNode0:hlink[dir=both color = red] LRUNode0:hlink->NULL2[color = red] LRUCache:hhead1->LRUNode1:hlink[dir=both color = blue] LRUNode1:hlink->NULL3[color = blue] } ``` --- ### Create Cache 1. Create `obj` pointer to newly allocated memory. 2. Initialize each element and return `obj`. ```c LRUCache *lRUCacheCreate(int capacity) { LRUCache *obj = malloc(sizeof(*obj) + capacity * sizeof(struct list_head)); obj->count = 0; obj->capacity = capacity; INIT_LIST_HEAD(&obj->dhead); for (int i = 0; i < capacity; i++) INIT_LIST_HEAD(&obj->hheads[i]); return obj; } ``` :::info When allocating memory, `sizeof(*obj)` is equaled to `2*sizeof(int)+sizeof(struct list_head)`. ::: --- ### Free cache Because `dhead` list contains every node, we can iterate through it and delete (remove and then free) each node. We need to delete the current node in each iteration, so we need the `safe` pointer to the next entry. ==MMM1: list_for_each_entry_safe== ```c void lRUCacheFree(LRUCache *obj) { LRUNode *lru, *n; MMM1 (lru, n, &obj->dhead, dlink) { list_del(&lru->dlink); free(lru); } free(obj); } ``` --- ### Get 1. Use modulo as hash function. 2. Iterate through the nodes in the corresponding bucket. 3. If found, move the node to the head of the `dhead` list (most recently used) and return the value. 4. If not found, return `-1`. ==MMM2: list_for_each_entry== ```c int lRUCacheGet(LRUCache *obj, int key) { LRUNode *lru; int hash = key % obj->capacity; MMM2 (lru, &obj->hheads[hash], hlink) { if (lru->key == key) { list_move(&lru->dlink, &obj->dhead); return lru->value; } } return -1; } ``` --- ### Put Similar to `lRUCacheGet`. 1. Use modulo as hash function. 2. Iterate through the nodes in the corresponding bucket. 3. If found, move the node to the head of the `dhead` list (most recently used) and **set the value in the node to input value**. 4. If not found: 1. If the cache is full, remove the last node in the `dhead` list (least recently used). 2. Add new `key` and `value` to cache. ==MMM3: list_for_each_entry== ==MMM4: list_last_entry== ```c void lRUCachePut(LRUCache *obj, int key, int value) { LRUNode *lru; int hash = key % obj->capacity; MMM3 (lru, &obj->hheads[hash], hlink) { if (lru->key == key) { list_move(&lru->dlink, &obj->dhead); lru->value = value; return; } } if (obj->count == obj->capacity) { lru = MMM4(&obj->dhead, LRUNode, dlink); list_del(&lru->dlink); list_del(&lru->hlink); } else { lru = malloc(sizeof(LRUNode)); obj->count++; } lru->key = key; list_add(&lru->dlink, &obj->dhead); list_add(&lru->hlink, &obj->hheads[hash]); lru->value = value; } ``` --- ### Test result with leetcode Add the following code at the top of the script. ```c #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) #define list_entry(node, type, member) container_of(node, type, member) #define list_last_entry(head, type, member) \ list_entry((head)->prev, type, member) #define list_for_each_entry(entry, head, member) \ for (entry = list_entry((head)->next, __typeof__(*entry), member); \ &entry->member != (head); \ entry = list_entry(entry->member.next, __typeof__(*entry), member)) #define list_for_each_entry_safe(entry, safe, head, member) \ for (entry = list_entry((head)->next, __typeof__(*entry), member), \ safe = list_entry(entry->member.next, __typeof__(*entry), member); \ &entry->member != (head); entry = safe, \ safe = list_entry(safe->member.next, __typeof__(*entry), member)) struct list_head { struct list_head *prev; struct list_head *next; }; typedef struct { int capacity, count; struct list_head dhead, hheads[]; } LRUCache; typedef struct { int key, value; struct list_head hlink, dlink; } LRUNode; void INIT_LIST_HEAD(struct list_head *head) { head->next = head; head->prev = head; } void list_add(struct list_head *node, struct list_head *head) { struct list_head *next = head->next; next->prev = node; node->next = next; node->prev = head; head->next = node; } void list_del(struct list_head *node) { struct list_head *next = node->next; struct list_head *prev = node->prev; next->prev = prev; prev->next = next; } void list_move(struct list_head *node, struct list_head *head) { list_del(node); list_add(node, head); } ``` ![](https://i.imgur.com/NLg7INN.png) --- ### Linux `lru_cache.h` :::warning I only understood a little. ::: lru_cache: `lru`: pointer to list of unused but ready to be reused or recycled element, the least recently used item is kept at `lru->prev` `free`: pointer to list of unused and ready to be recycled element `in_use`: pointer to list of element in use `to_be_changed`: pointer to `lc_cache`: pointer to `struct kmem_cache` `element_size`: size of tracked objects `element_off`: offset of `struct lc_element` member in the tracked object `nr_elements`: number of elements (indices) `max_pending_changes`: allow to accumulate number of changes `pending_changes`: number of elements currently on to_be_changed list `used`: number of elements currently on in_use list `hits, misses, starving, locked, changed`:number of elements `flags`: flag bits (enum) for lru_cache `lc_private`: `name`: `lc_slot`:`nr_elements` there `lc_element`:`nr_elements` there `lc_find`: find which `lc_slot` bucket the element is in using `lc_new_number` as key. `lc_get()`: 1. Find the element using `lc_find()`. 1. If found, increase `hits` and other statistics then move the element to `in_use` list. 1. If not found, increase `miss`. `lc_set()`: associate index with label, move element to `lru` list or `free` list. `lc_put()`: decrease `refcnt`, if `refcnt == 0`, move element to `lru` list. ```graphviz digraph G{ rankdir=LR compound = true node[shape = record] subgraph cluster1{ style = filled color = gray90 label = lru_cache lru_cache[label = "lru|free|in_use|to_be_changed|<lc_cache>lc_cache|element_size|element_off|nr_elements|max_pending_changes|pending_changes|used|hits|misses|starving|locked|changed|flags|lc_private|name|<lc_slot>lc_slot|<lc_element>lc_clcment"] } subgraph cluster2{ style = filled color = gray90 label = kmem_cache kmem_cache } subgraph cluster0{ style = filled color = gray90 label = lc_element lc_element0[label = "colision|list|refcnt|lc_index|lc_number|lc_new_number"] } subgraph cluster3{ style = filled color = gray90 label = lc_element lc_element3[label = "colision|list|refcnt|lc_index|lc_number|lc_new_number"] } subgraph cluster5{ style = filled color = gray90 label = slot slot[label = "<0>hlist_head[0]|<1>hlist_head[1]|..."] } subgraph cluster4{ style = filled color = gray90 label = element element[label = "<0>[0]|<1>[1]|..."] } lru_cache:lc_cache->kmem_cache lru_cache:lc_element->element[lhead = cluster4] element:0->lc_element0[lhead = cluster0] element:1->lc_element3[lhead = cluster3] lru_cache:lc_slot->slot[lhead = cluster5] } ``` --- ## 4. Longest Consecutive Sequence ### Example code 1. Iterate through `nums` and put each num in hash table. 2. Because of the hash function, consecutive num will be place in adjacent bucket. 3. For each key: 1. Check if previous bucket contains `key-1`, if so, repeat for `key-2` and so on until the key is not found, record how many are found. ==`LLL: --left`== 3. Check if next bucket contains `key+1`, if so, repeat for `key+2` and so on until the key is not found, record how many are found. ==`RRR: --right`== 4. Return the longest number. ```c #include <stdio.h> #include <stdlib.h> #include "list.h" struct seq_node { int num; struct list_head link; }; static struct seq_node *find(int num, int size, struct list_head *heads) { struct seq_node *node; int hash = num < 0 ? -num % size : num % size; //bit-wise op list_for_each_entry (node, &heads[hash], link) { if (node->num == num) return node; } return NULL; } int longestConsecutive(int *nums, int n_size) { int hash, length = 0; struct seq_node *node; struct list_head *heads = malloc(n_size * sizeof(*heads)); for (int i = 0; i < n_size; i++) INIT_LIST_HEAD(&heads[i]); for (int i = 0; i < n_size; i++) { if (!find(nums[i], n_size, heads)) { hash = nums[i] < 0 ? -nums[i] % n_size : nums[i] % n_size; //bit-wise op node = malloc(sizeof(*node)); node->num = nums[i]; list_add(&node->link, &heads[hash]); } } for (int i = 0; i < n_size; i++) { int len = 0; int num; node = find(nums[i], n_size, heads); while (node) { len++; num = node->num; list_del(&node->link); int left = num, right = num; while ((node = find(LLL, n_size, heads))) { len++; list_del(&node->link); } while ((node = find(RRR, n_size, heads))) { len++; list_del(&node->link); } length = len > length ? len : length; } } return length; } ``` ### Improvement The above method takes $O(n^2)$ because for each number we have to iterate again to know the length of each consecutive sequence We can achieve $O(n)$ if we record the consecutive sequence when inserting the number in the bucket. ```graphviz digraph G{ node[shape = record] subgraph cluster0{style = filled color = gray90 label = map map[label="{<0>map[0]|...|<5>map[map_size-1]}"] } subgraph cluster1{style = filled color = gray90 label = setNode setNode[label = "{key|min|max|link}"] } } ``` Consider the following example: ```graphviz digraph G{ compound = true node[shape = record] rankdir = LR subgraph cluster0{style = filled color = gray90 label = map map[label="<0>map[0]|<1>map[1]|<2>map[2]|<3>map[3]|<4>map[4]|<5>map[5]"] } subgraph cluster1{style = filled color = gray90 label = setNode setNode1[label = "{key=1|min=1|max=2|link}"] } subgraph cluster2{style = filled color = gray90 label = setNode setNode2[label = "{key=2|min=1|max=2|link}"] } subgraph cluster4{style = filled color = gray90 label = setNode setNode4[label = "{key=4|min=4|max=5|link}"] } subgraph cluster5{style = filled color = gray90 label = setNode setNode5[label = "{key=5|min=4|max=5|link}"] } map:1->setNode1[lhead = cluster1] map:2->setNode2[lhead = cluster2] map:4->setNode4[lhead = cluster4] map:5->setNode5[lhead = cluster5] } ``` If we insert 3, we need to update`map[3]`, `map[1]` and `map[5]` and don't care about the other keys. Because 1~5 is a known consecutive sequence, if 1~5 is inserted again, nothing will be changed. ```graphviz digraph G{ compound = true node[shape = record] rankdir = LR subgraph cluster0{style = filled color = gray90 label = map map[label="<0>map[0]|<1>map[1]|<2>map[2]|<3>map[3]|<4>map[4]|<5>map[5]"] } subgraph cluster1{style = filled color = gray90 label = setNode setNode1[label = "{key=1|min=1|max=5|link}" color =red] } subgraph cluster2{style = filled color = gray90 label = setNode setNode2[label = "{key=2|min=1|max=2|link}" ] } subgraph cluster3{style = filled color = gray90 label = setNode setNode3[label = "{key=3|min=1|max=5|link}" color =red] } subgraph cluster4{style = filled color = gray90 label = setNode setNode4[label = "{key=4|min=4|max=5|link}"] } subgraph cluster5{style = filled color = gray90 label = setNode setNode5[label = "{key=5|min=1|max=5|link}" color =red] } map:1->setNode1[lhead = cluster1] map:2->setNode2[lhead = cluster2] map:3->setNode3[lhead = cluster3] map:4->setNode4[lhead = cluster4] map:5->setNode5[lhead = cluster5] } ``` ```c #define container_of(ptr, type, member) \ __extension__({ \ const __typeof__(((type *) 0)->member) *__pmember = (ptr); \ (type *) ((char *) __pmember - offsetof(type, member)); \ }) struct hlist_node { struct hlist_node *next, **pprev; }; struct hlist_head { struct hlist_node *first; }; typedef struct{ int key, min, max; struct hlist_node link; } setNode; struct hlist_head *map_init(size_t map_size) { struct hlist_head *map = malloc(map_size * sizeof(struct hlist_head *)); for (size_t i = 0; i < map_size; i++){ map[i].first = NULL; } return map; } void map_deinit(struct hlist_head *map , size_t map_size){ for (size_t i = 0; i < map_size; i++){ struct hlist_node *target = map[i].first; while (target) { map[i].first = target->next; free(container_of(target, setNode, link)); target = map[i].first; } } free(map); } void map_add(struct hlist_head *map, int key, size_t map_size, int min, int max){ int hash = key % map_size; struct hlist_node *head = map[hash].first; setNode * e = malloc(sizeof(setNode)); e->min = min; e->max = max; e->key = key; e->link.next = head; if(head) head->pprev = &e->link.next; map[hash].first = &e->link; e->link.pprev = &map[hash].first; } setNode *map_find(struct hlist_head *map, int key, size_t map_size){ int hash = key % map_size; struct hlist_node *head = map[hash].first; setNode *ret = NULL; for(;head; head = head->next) { ret = container_of(head, setNode, link); if (ret->key == key) return ret; } return NULL; } int longestConsecutive(int* nums, int numsSize){ int ret = 0; size_t map_size = 50000; struct hlist_head *map = map_init(map_size); for(int i=0; i < numsSize; ++i) { if(map_find(map,nums[i],map_size)) continue; setNode *set1 = map_find(map, nums[i]-1, map_size); setNode *set2 = map_find(map, nums[i]+1, map_size); int min = set1 ? set1->min : nums[i]; int max = set2 ? set2->max : nums[i]; map_add(map, nums[i], map_size, min, max); if (min != nums[i]) map_add(map, min, map_size, min, max); if(max != nums[i]) map_add(map, max, map_size, min, max); ret = ret > max - min + 1 ? ret : max - min + 1; } // map_deinit(map, map_size); return ret; } ``` ### Test result ![](https://i.imgur.com/kH6kcsd.png)