--- tags: linux2022 --- # 2022q1 Homework1 (quiz1) contributed by < `YiChainLin` > > [測驗題目](https://hackmd.io/@sysprog/linux2022-quiz1) ## 測驗2 [Leetcode : 82. Remove Duplicates from Sorted List II](https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/),以下是可能的合法 C 程式實作: * 所使用的 `linked list` 結構為單向連結的方式 ```c struct ListNode { int val; struct ListNode *next; }; ``` * 完整程式 ```c #include <stddef.h> struct ListNode { int val; struct ListNode *next; }; struct ListNode *deleteDuplicates(struct ListNode *head) { if (!head) return NULL; if (COND1) { /* Remove all duplicate numbers */ while (COND2) head = head->next; return deleteDuplicates(head->next); } head->next = deleteDuplicates(head->next); return head; } ``` ``` COND1 = head->next && head->val == head->next->val COND2 = head->next && head->val == head->next->val ``` * 題目採取遞迴的方式實作 * 思考要點: * 進入判斷式的條件應要有與下一個節點元素相同的時候成立 * 若是重複時則 `linked list` * 用以下範例解釋運作的過程 * 原始的狀態: 1. `head` 指向第一個元素,其值為 1 ,與 `head->next->val` 的值做比較,所以 `1 != 2` 因此將下一個元素 `head->next` 進入遞迴 ```graphviz digraph graphname{ node[shape=box] head[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir=LR { rankdir = LR head[label=head] A[label=1] B[label=2] C[label=2] D[label=4] E[label=7] } head->A->B->C->D->E->NULL } ``` 2. `head` 指向第二個元素,其值為 2 ,與 `head->next->val` 的值做比較,所以 `2 == 2` 進入判斷式中 ```graphviz digraph graphname{ node[shape=box] head[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir=LR { rankdir = LR head[label=head] A[label=1] B[label=2] C[label=2] D[label=4] E[label=7] } head->B A->B->C->D->E->NULL } ``` 3. 判斷式中執行 `head = head->next` , (注意:這裡的 `head` 是遞迴上一層的 `head->next` ),然後在進行下兩個元素的判斷, `2 != 4` 所以重複元素的判斷已結束 ```graphviz digraph graphname{ node[shape=box] head[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir=LR { rankdir = LR head[label=head] A[label=1] B[label=2] C[label=2] D[label=4] E[label=7] } head->C A->B->C->D->E->NULL } ``` 4. `head = head->next` 走訪至第四個節點後返回此沒有重複的節點 ```graphviz digraph graphname{ node[shape=box] head[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir=LR { rankdir = LR head[label=head] A[label=1] B[label=2] C[label=2] D[label=4] E[label=7] } head->D A->B->C->D->E->NULL } ``` 5. `head->next = deleteDuplicates(head->next);` 返回值為 `val = 4` 的節點,使一開始的 `head` 連結到第四個元素,但是重複的元素只是被移除( `remove` )並未被刪除( `delete` ),持續的遞迴的操作將把所有重複的元素移除 ```graphviz digraph graphname{ node[shape=box] head[shape=plaintext,fontcolor=red] rankdir=LR { rankdir = LR head[label=head] NULL[shape=plaintext,fontcolor=black] A[label=1] B[label=2] C[label=2] D[label=4] E[label=7] } head->A A->D->E->NULL B->C } ``` * 非遞迴的做法: 將上述的遞迴型式改用非遞迴的方式,在效能上會有比較好的表現 * 思考方式 * 將重複前的節點先存下來,使用一個指標進行重複點的走訪 * 將暫存節點換連至未重複點 ```c #include <stddef.h> struct ListNode { int val; struct ListNode *next; }; struct ListNode* deleteDuplicates(struct ListNode* head){ if (!head) return NULL; struct ListNode *tmp = malloc(sizeof(struct ListNode)); tmp->next = head; struct ListNode *prev = tmp; struct ListNode *cur = head; while (cur) { while (cur->next && cur->val == cur->next->val) { cur = cur->next; } if (prev->next == cur) prev = prev->next; else prev->next = cur->next; cur = cur->next; } return tmp->next; } ``` 1. 建立一個 `tmp` 的節點,使 `tmp->next = head` 指向 `head` ,建立兩個指標分別為 `prev` 與 `cur` 作為走訪節點用途 ```graphviz digraph graphname{ node[shape=box] head[shape=plaintext,fontcolor=black] prev[shape=plaintext,fontcolor=red] cur[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir=LR { rankdir = LR head[label=head] A[label=1] B[label=2] C[label=2] D[label=4] E[label=7] tmp[label=tmp] } prev->tmp cur->head tmp->head->A->B->C->D->E->NULL } ``` 2. `while` 迴圈中的判斷條件為 `cur->next && cur->val == cur->next->val` 所以 `head != 1` 並未有重複點的情況,因此兩個指標個都向前走訪一個節點 `prev = prev->next;` `cur = cur->next;` ```graphviz digraph graphname{ node[shape=box] head[shape=plaintext,fontcolor=black] prev[shape=plaintext,fontcolor=red] cur[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir=LR { rankdir = LR head[label=head] A[label=1] B[label=2] C[label=2] D[label=4] E[label=7] tmp[label=tmp] } prev->A cur->B tmp->head->A->B->C->D->E->NULL } ``` 3. `cur` 指標遇到重複點進入 `while` 迴圈中持續走訪重複的節點,直到不重複 ```graphviz digraph graphname{ node[shape=box] head[shape=plaintext,fontcolor=black] prev[shape=plaintext,fontcolor=red] cur[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir=LR { rankdir = LR head[label=head] A[label=1] B[label=2] C[label=2] D[label=4] E[label=7] tmp[label=tmp] } prev->A cur->C tmp->head->A->B->C->D->E->NULL } ``` 4. 此時 `prev->next` 指標指向未重複點也就是 `cur->next` 完成重複點的移除,移除所有的重複點後, `return tmp->next` 也就是原本的 `head` ```graphviz digraph graphname{ node[shape=box] head[shape=plaintext,fontcolor=black] prev[shape=plaintext,fontcolor=red] cur[shape=plaintext,fontcolor=red] NULL[shape=plaintext,fontcolor=black] rankdir=LR { rankdir = LR head[label=head] A[label=1] B[label=2] C[label=2] D[label=4] E[label=7] tmp[label=tmp] } prev->A cur->D B->C tmp->head->A->D->E->NULL } ``` * 改寫成 circular doubly-linked list 學生在 [lab0-c](https://hackmd.io/@YiChianLin/linux2022-lab0)中有實作,當中有程式運行的流程 ```c bool q_delete_dup(struct list_head *head) { if (!head || list_is_singular(head)) return false; struct list_head *tmp = head->next; struct list_head *if_dup = NULL; while (tmp != head) { element_t *ele_dup = list_entry(tmp, element_t, list); element_t *ele_dup_next = list_entry(tmp->next, element_t, list); while (!strcmp(ele_dup->value, ele_dup_next->value)) { if_dup = tmp; list_del(tmp->next); q_release_element(ele_dup_next); ele_dup_next = list_entry(tmp->next, element_t, list); if (tmp->next == head) break; } tmp = tmp->next; if (if_dup) { element_t *ele_dup_a = list_entry(if_dup, element_t, list); list_del(if_dup); q_release_element(ele_dup_a); if_dup = NULL; } if (tmp->next == head) break; } return true; } ``` ## 測驗3 [Leetcode : 146. LRU Cache](https://leetcode.com/problems/lru-cache/),以下是可能的合法 C 程式實作: * LRU 全名為 [least recently used](https://zh.wikipedia.org/wiki/%E5%BF%AB%E5%8F%96%E6%96%87%E4%BB%B6%E7%BD%AE%E6%8F%9B%E6%A9%9F%E5%88%B6),在 cache 容量滿了的情況,將最久沒使用的資料刪除,已能空出新的空間 ```c #include <stdio.h> #include <stdlib.h> #include "list.h" typedef struct { int capacity, count; struct list_head dhead, hheads[]; } LRUCache; typedef struct { int key, value; struct list_head hlink, dlink; } LRUNode; 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; } void lRUCacheFree(LRUCache *obj) { LRUNode *lru, *n; MMM1 (lru, n, &obj->dhead, dlink) { list_del(&lru->dlink); free(lru); } free(obj); } 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; } 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; } ``` * 使用到 [list.h](https://github.com/torvalds/linux/blob/master/include/linux/list.h)中 `doubly circular-linked list` 的結構 ```c struct list_head { struct list_head *next struct list_head *prev; }; ``` ```c typedef struct { int capacity, count; struct list_head dhead, hheads[]; } LRUCache; ``` ```c typedef struct { int key, value; struct list_head hlink, dlink; } LRUNode; ```