# 2025 年[「資訊科技產業專案設計」](https://hackmd.io/@sysprog/info2025-homework2)作業 2
Contributor: <`托爾斯泰-Tolstoy`>
## [LeetCode 450: Delete Node in a BST ](https://leetcode.com/problems/delete-node-in-a-bst/description/)
🧑🏻:
> 沒問題的話,那我們現在就開始code interview的部分
🧑🏼🎓:
> 好的
🧑🏻:
> 我們來做一到關於二元搜尋數Binary search Tree的題目
你需要製作一個函式叫做DeleteNode刪除節點
然後input式root樹根還有key,型態為int
請你實作刪除值為key的節點,並回傳新的根節點
若不存在這個key值的點,則回傳原本的root
若存在請根據BST的性質正確的刪除他
🧑🏼🎓:
> 了解了!我會想用遞迴的方法
> 首先排除root為空的情況
接著先討論如果當下看得節點(root)值不為key時
根據BST的性質,若key比較大則往右子樹遞迴,反之往左子樹遞迴
若找到了,也就是key等於當下root的值
> 這裡我在分為三種情況
第一種是左子樹為空,那就把右子樹的root回傳
第二種則是右子樹為空,那就把左子樹的root回傳
> 第三種是左右子樹都存在,那麼我就會把當下target點右子樹中最小的節點拉上來
這裡我會想用一個while loop來完成
並且做法是找到這一個右子樹最小的節點後,把他的值貼上來target節點
接著對右子樹呼叫deleteNode函式
因為這個右子樹最小的節點一定沒有左子樹,所以一定會解決
如此就能完成BST刪除節點的實作
> 我的想法講完了,接下來就來打我的code!
```cpp
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if(root==NULL) return root;
else if(root->val<key) // 目標點在右子樹
root->right=deleteNode(root->right,key);
else if(root->val>key)
root->left=deleteNode(root->left,key);
else{ // 找到target node!
if(root->left==NULL)
return root->right;
if(root->right==NULL)
return root->left;
else{ // 左右子樹都在
TreeNode* rightsmallest=root->right;
while(rightsmallest->left!=NULL)
rightsmallest=rightsmallest->left;
root->val=rightsmallest->val;
root->right=deleteNode(root->right,rightsmallest->val);
}
}
return root;
}
};
```
🧑🏻:
> OK好,謝謝你的回答!
## [LeetCode 146: LRU Cache](https://leetcode.com/problems/lru-cache/)
**Interviewer**: 好,希望前面的講解能讓你更了解我們公司一點,那接下來就不免俗地來進行我們的code interview。那這一題我想要來考驗一下有關OS的能力,來做一個LRU Cache的實做。這一題我們希望你實做使用LRU,簡單版的cache,然後我的資料會是int,相對應的key值,需要你實做的有3個函式。
會有一個LRU class來實做,裡面有3個函式,第一個是建構子去建構LRU Cache,第二個是取得資料,第三個是把資料放進去。然後LRU的建構子會給你一個capacity,就是它的容量大小,超過容量大小的話,你應該知道要怎麼做,那接下來就交給你來實做。
**Interviewee**: LRU 是一種快取的淘汰策略,當快取滿了以後,我們會刪除「最久沒有被使用」的項目,以便騰出空間給新的資料。
為了達成這個目標,我們需要同時**滿足兩個條件**:
1. **能夠** O(1) **查找 key 是否存在**。
我們可以使用 `unordered_map`(也就是 Hash Map),讓我們能夠直接找到對應的node。
2. **能夠** O(1) **調整使用順序**。
這部分我們用 **Doubly Linked List** 來完成。每當有節點被使用時,就把它移到最前面。而當容量不足時,我們只要從尾端刪除最後一個節點,就是「最久沒被用」的項目。
我會在 linked list 的開頭與尾端各放一個虛擬節點(`head` 和 `tail`),這樣可以讓插入與刪除節點的操作更簡單,也不用處理太多邊界條件。
那接著我就開始實做。
> 我先從 linked list 開始
>
```cpp
#include <unordered_map> // include 給 hash map 的 library
class Node { // 我先定義一個 Node class
public: // 它會有 key、value、next、prev。
int key;
int value;
Node *next;
Node *prev;
Node(int k = 0, int v = 0) : key(k), value(v), next(nullptr), prev(nullptr) {}
// 另外我會提供兩個 utility function
void append(Node *target) { // 把target node接到current node後面。
this->next = target;
if (target)
target->prev = this;
}
void pull() { // 把node從linked list中拔出來。
if (next)
next->prev = prev;
if (prev)
prev->next = next;
}
};
```
> 接著我來實作 `LRUCache` class
```cpp
class LRUCache {
private:
// 我需要一個 unordered_map<int, Node*> 來記錄 key 對應的節點。
std::unordered_map<int, Node *> cache_map;
// 同時,我會有2個虛擬節點 head 和 tail 方便管理linked list。還有cache的空間。
Node *head;
Node *tail;
int capacity;
// 將節點移到最前面(最近使用)
void moveToFront(Node *node) {
node->pull(); // 拿出來
node->append(head->next); // 處理後面的節點
head->append(node); // 處理head節點
}
public: // Constructor
LRUCache(int capacity) : capacity(capacity) {
head = new Node();
tail = new Node();
head->append(tail);
}
```
接下來我實作 `get()`。
```cpp
int get(int key) {
// Cache miss: 如果 key 不存在,我直接回傳 -1。
std::unordered_map<int, Node *>::iterator it = cache_map.find(key);
if (it == cache_map.end()) {
return -1;
}
// Cache hit,如果存在,就把節點移到最前面,然後回傳它的值。
Node *node = cache_map[key];
moveToFront(node);
return node->value;
}
```
然後是 `put()`。
```cpp
void put(int key, int value) {
// 如果 key 已存在,更新值並移到最前面
std::unordered_map<int, Node *>::iterator it = cache_map.find(key);
if (it != cache_map.end()) {
Node *node = cache_map[key];
node->value = value;
moveToFront(node);
return;
}
// 創建新節點
Node *new_node = new Node(key, value);
cache_map[key] = new_node; // 登記到hash map中
// 檢查容量
if (capacity > 0) { // 如果足夠就capacity扣除1
capacity--;
} else {
// 容量不足,刪除最久未使用的節點,也是最後一個節點
Node *discard = tail->prev; // 可以用tail的前一個節點表示
if (discard != head) { // 排除掉 empty list 的可能性
discard->pull(); // 從 cache 移除
cache_map.erase(discard->key); // 從 hash map 中移除
}
}
// 將新節點加到最前面
new_node->append(head->next);
head->append(new_node);
}
};
```
最後我做簡單的測試來驗證:
```cpp
#include <iostream>
// 測試程式
int main() {
LRUCache *cache = new LRUCache(2); // 容量為2的cache
// 接著插入1和2
cache->put(1, 1);
cache->put(2, 2);
// 存取1,這樣一來2就是Least Recently Used cache
printf("Get 1: %d\n", cache->get(1));
// 插入3,然後2會被替換
cache->put(3, 3);
// 看看2是不是cache miss並回傳-1
printf("Get 2: %d\n", cache->get(2));
// 我們再試著插入4,1是LRU會被替換
cache->put(4, 4);
printf("Get 1: %d\n", cache->get(1));
printf("Get 3: %d\n", cache->get(3));
printf("Get 4: %d\n", cache->get(4));
return 0;
}
```
整體的思路是透過 map 快速找到節點位置,再用雙向鏈結串列維持 LRU 順序。所有操作都在 O(1) 時間內完成。
---
**Interviewer**: 不好意思我想要看一下你`get()`那邊的程式碼
看到了,程式碼完成度滿高的,有一點點小錯誤但你有找到那個==,然後看到你有少一個分號之類的,但是沒有關係,程式整體是存在的,code interview就到這邊,感謝你的用心講解。
# 自評
## Interviewee
- 有發現自己的`!=`沒有打好
- 可以省去程式碼比較繁複的步驟,因為沒有真的要實際執行,所以像 linked list 的部分可以假設有宣告就好
## Interviewer
- 能問 interviewee 的問題不足,比如可以問複雜度等等的問題。
# 檢討自己至今的表現
## 在作業上
最近在練習模擬面試的時候,我開始更注重表達和互動節奏。以前準備面試時,我只會背答案,結果一緊張就容易忘。現在我會先整理自己的想法,用框架去幫助自己講得更自然,比如先說背景、再講做法、最後補上結論。即使臨場被問到沒準備的題目,也能靠著邏輯去組織出一個有方向的回答,而不是卡住。
我也開始注意到和 interviewer 的互動很重要。以前我講完就等下一題,現在會試著多觀察對方的反應並向對方確認理解程度,讓整個對話變得更自然。這樣不只是表現得比較放鬆,也能讓面試官感覺我真的在「溝通」,而不是在「背稿」。
另外,我有刻意練習自己的語調和肢體語言。比如講到有趣的應用時會讓表情自然一點、語氣有起伏。
## 在上課中
這堂課讓我收穫最多的一點,是有機會能直接和業界的學長姐交流。以前在上課時,常常覺得內容離實際工作很遠,但透過他們的分享,我開始更清楚這些底層知識在真實開發裡的重要性。比如他們講到在公司裡如何處理效能瓶頸、系統架構的取捨,讓我發現很多理論其實都能對應到實際情境,只是以前沒有機會那樣思考。
這門課讓我學會怎麼思考和提問問題」,未來在面試或職場上,這樣的能力會比單純背知識更有價值。