---
tags: INFO2022
---
# 課堂問答簡記: 2022-10-18
## 案例: [104. Maximum Depth of Binary Tree](https://leetcode.com/problems/maximum-depth-of-binary-tree/)
* [噠噠特工-LonelyBoy](https://hackmd.io/@sysprog/S1gP445eGs)
* Q1: 問題在哪裡? 如何舉例? 如何驗證?
* [4:25](https://www.youtube.com/watch?v=sd2SuF743PI&t=4m25s) 舉例花太多時間: 可以舉更簡單的例子說明,以下圖為例:從 A 開始先往左邊找,B 也先往左邊找,為 NULL 因此檢查 B 的右邊,亦為 NULL 因此回到 A,接著找 A 的右邊,為 NULL,結束。
```
A
/
B
```
* [2:50](https://www.youtube.com/watch?v=8FgfWqh54OE&t=2m50s) 舉例時可以先用英文字母 ABC 取代數字,這樣用英語面試比較不容易口誤。
* [9:02](https://www.youtube.com/watch?v=sd2SuF743PI&t=9m02s) 驗證花太久時間 (4分半鐘): 以遞迴的方法來說,我們在意兩個條件,一是遞迴關係式,二是終止條件。因此在舉例驗證時可以直接舉開頭的節點來說明遞迴關係,接著舉一個 leaf node 說明遞迴終止條件,其他的節點可以用『以此類推』代過,節省面試時間 (因為後面可能還有 2, 3 題 followup 等你來解)
* Q2: DPS/BFS 解法
* BFS 解法
* 好處
* 程式碼彈性較大,如果 followup 出一個變化題 (e.g., 找到 binary tree 的某個節點並刪除) 可以透過小修改當前程式碼快速實作完成,節省時間,以免後續 followup 解不完。
* 壞處
* 程式碼相對較長
* 思路相對不直覺
```cpp=
// BFS
int maxDepth(TreeNode *root) {
std::queue<TreeNode*> q;
int level = 0;
int cur_count;
if (root) {
q.push(root);
cur_count = 1;
}
while (!q.empty()) {
// Pop first element from queue.
auto ptr = q.front();
q.pop();
cur_count--;
// Push children to queue.
if (ptr->left) {
q.push(ptr->left);
}
if (ptr->right) {
q.push(ptr->right);
}
// Elements in the current level is
// traversed. Advance to next level.
if (cur_count == 0) {
cur_count = q.size();
level++;
}
}
return level;
}
```
:::spoiler **(點擊展開: BFS 方法 2)** 利用 NULL 判斷 all current level nodes popped out,程式碼會再精簡一點
:::info
[**BFS Algorithm**](https://en.wikipedia.org/wiki/Breadth-first_search):
* **Initial state**: ++enqueue root++ and ++enqueue NULL++ (use NULL to record all current level nodes have marked)
* **Iterative state**:
* ++dequeue front node v++
* for all edges from v to w in G.adjacentEdges(v) exist
* ++enqueue child node w++
* if v is NULL, then ++depth\+\+++
* if q is not empty, ++enqueue NULL++
* else ++return depth++
```cpp
int maxDepth(TreeNode *root) {
std::queue<TreeNode*> q;
int depth = 0;
if (root) {
q.push(root);
q.push(NULL);
}
while (!q.empty()) {
// Pop first element from queue.
auto v = q.front();
q.pop();
// Push children to queue.
if (v) {
if (v->left)
q.push(v->left);
if (v->right)
q.push(v->right);
}
// Current level nodes have traversed.
// Advance to next level.
else {
depth++;
// if queue is empty, mean this is bottom.
if (!q.empty())
q.push(NULL);
}
}
return depth;
}
```
:::
BFS 解刪除 binary 指定結點,只要新增 18 ~ 27 行。
```cpp=
// Find the node with value equal to target.
// If you found it, delete that node and its children.
int deleteNode(TreeNode *root, int target) {
std::queue<TreeNode*> q;
int level = 0;
int cur_count;
if (root) {
q.push(root);
cur_count = 1;
}
while (!q.empty()) {
auto ptr = q.front();
q.pop();
cur_count--;
// Add this.
if (ptr->left && ptr->left->value == target) {
ptr->left = nullptr;
break;
}
// Add this.
if (ptr->right && ptr->right->value == target) {
ptr->right = nullptr;
break;
}
if (ptr->left) {
q.push(ptr->left);
}
if (ptr->right) {
q.push(ptr->right);
}
if (cur_count == 0) {
cur_count = q.size();
level++;
}
}
return level;
}
```
* DFS (遞迴) 的解法
* 好處:
* 程式碼較 BFS 簡潔
* 思路直覺
* 壞處:
* 遞迴深度過深記憶體會出問題
* 面對 followup 的變化題較無彈性,沒辦法透過小小的修改程式碼解決變化題
## 案例: [9. Palindrome Number](https://leetcode.com/problems/palindrome-number/)
* [秘客-Cypto](https://hackmd.io/@sysprog/BkG_gclMs)
## 案例: [217. Contains Duplicate](https://leetcode.com/problems/contains-duplicate/)
* [起司堡-cheeseburger](https://hackmd.io/@sysprog/Bkdiazbzs)
* Q1: 若不能用 hash table,那要用甚麼策略?
* 排序 -> x
* set
* priority queue
* merge sort合併時檢查是否有相同數字
* 不要過度糾結於複雜度,更重要的是展現靈活應變
* 可以於建立 Heap 的過程中都檢查 current node 與 children nodes 是否有重複的數值,便有機會提早找到。完整建立 Heap $O(\frac{n}{2})$。若未找到,則後續再使用 Heap Sort $O(n \cdot{ \log_{2}}n)$。以下提供 C 語言實作參考:
```c
// build a maxheap O(n/2)
// heap sort O(n * logn)
void swap(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
// top-down, recurrsion
bool heapify(int *heap, int heapSize, int parentIndex)
{
// base case
int candidateIndex = parentIndex;
int leftchildIndex = 2 * parentIndex + 1;
int rightchildIndex = 2 * parentIndex + 2;
if ((leftchildIndex < heapSize) && heap[leftchildIndex] > heap[candidateIndex])
candidateIndex = leftchildIndex;
if ((rightchildIndex < heapSize) && heap[rightchildIndex] > heap[candidateIndex])
candidateIndex = rightchildIndex;
if (candidateIndex == parentIndex)
return false;
// recurrsive steps
// early stop
if ((heap[parentIndex] == heap[leftchildIndex]) || (heap[parentIndex] == heap[leftchildIndex]))
return true;
// heapify
swap(&heap[candidateIndex], &heap[parentIndex]);
return heapify(heap, heapSize, candidateIndex);
}
bool containsDuplicate(int *nums, int numsSize)
{
for (int i = (numsSize-2) / 2; i >= 0; i--) {
// build heap O(n/2)
if (heapify(nums, numsSize, i)) // early stop
return true;
}
for (int tail = numsSize-1; tail > 0; tail--) {
// heap sort O(n * logn)
swap(&nums[0], &nums[tail]);
if (heapify(nums, tail, 0))// early stop, tail equal current heap size
return true;
if (nums[0] == nums[tail]) // early stop
return true;
}
return false;
}
```