# 2023 年「[資訊科技產業專案設計](https://hackmd.io/@sysprog/info2023)」作業 1
> 貢獻者: 瞧滷肉GIOGIO
> [模擬面試影片-1(漢)](https://www.youtube.com/watch?v=PnLzfA89Bu8)
> [模擬面試影片 (英)](https://www.youtube.com/watch?v=zdfKiC64hJs)
> [模擬面試影片-2(漢)](https://www.youtube.com/watch?v=SmPKBypc4vs)
## **572. Subtree of Another Tree**
- **Ref**
- https://www.youtube.com/watch?v=E36O5SWp-LE
* **Description** : 給定一個Tree_A,跟另一個比較小Tree_B,給定一個算法判斷B是否為A之subtree
- ***DFS***
* **Solution**
利用DFS走訪Tree_A,比較**每個node.value是否跟Tree_B的root具備相同value**
1. ***若無***,則往下以DFS走訪Tree_A
- **A還沒走完** : 繼續找
- **A已經找完** : 不存在跟Tree_B相同子樹, $return false$ 結束程式
2. ***若相等***,則呼叫比對函數(isSame)以DFS走法同步進行比對(遞迴呼叫),此時主函數會得到以下兩種返回值
- **false** : 回到 $step(1)$ 持續走訪Tree_A
- **true** : 確認 Tree-B 為 Tree_A 子樹,直接結束程式
```python
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) -> bool:
def isSame(node,sub):
if not node and not sub:
return True
elif not node or not sub:
return False
if node.val != sub.val:
return False
return isSame(node.left,sub.left) and isSame(node.right,sub.right)
def preorder(node):
if node == None :
return False
if node.val == subRoot.val:
if isSame(node,subRoot) :
return True
return (preorder(node.left) or preorder(node.right))
return preorder(root)
```
* **Complexity Analysis**
- 設A樹 $n$ 個 $node$ , B樹 $m$ 個 $node$
- DFS走訪A子樹需要的複雜度$O(n)$,每次都有可能呼叫比對函數(isSame),複雜度$O(m)$
- 時間複雜度 : 為$O(n \cdot m)$
- 空間複雜度 : A樹的最大樹高$n$ $+$ B樹的最大樹高 $m$ $\implies$ $O(n+m)$
- ***Hash***
* **Solution**
1. 先做出一個hash function
2. 利用DFS 走訪Atree, 當檢查該節點之子節點後紀錄其hash funciton的值 計算完後, 先比對Btree的hash值 if true 結束, else 在return hash值回上一層繼續算
* **Complexity Analysis**
* 時間複雜度 :$O(n)$
* 空間複雜 : $O(1)$
```
對答草稿
🦁 : interviewer Leo
🐵 : interviewee Sam
🦁 : 嗨,你好,我是擔任這場面試的考官,我的名字叫XXX,首先很高興跟你進行這場面談,那如果沒有問題,我們就開始第一道問答
🐵 : 好
🦁 : 第一道問題是難度簡單的題目,內容是關於tree,根據螢幕中的資料,給定一個treeA,跟另外一個較小的treeB,請給出一個算法,判斷TreeB是否為treeA的子樹。
另外 1.注意treeA的節點數落在1~2000,treeB則是落在1~1000之間。
2.節點中的值都是落在-10^4 ~ 10^4之間
3.這裡的tree都是二元樹
🐵 : 第一個想請問,這裡給定的樹是否有規定是二元搜索樹,或是Balance-tree,或是有其他如紅黑樹左偏樹的性質嗎?
🦁 : 沒有就是單純的二元樹,沒有其他特殊的性質
🐵 : 恩好的,那目前最直覺的做法,先利用DFS的走法,先查找A中的每個節點的值,那當找到與treeB root.val相同的值時,在呼叫一個比對函數進行比對如果return值是true,代表找到從該節點下方就是像treeB一樣的tree,false就代表從這個節點的下方並不是treeB,接著再繼續找,直到DFS搜索完整個treeA
🦁 : 好的,那是否可以分析一下這個算法的時間與空間複雜度
🐵 : 因為整個treeA 有 n 個節點,而treeB有 m 個節點,故時間複雜度是 O(nm),而因為實作方式是利用遞迴,因此呼叫程式所需要之stack複雜度為 O(n+m) , 而不需要額外的空間儲存資訊O(1)
🦁 : 恩!這是一個很直覺的算法,但今天想改進時間複雜度,是否能再提出更好的做法
🐵 : 請問這裡能允許使用額外的記憶體空間作紀錄嗎?
🦁 : 可以
🐵 : 恩... 這裡或許可以利用一個hash進行記錄,因為當給定一個tree的前+中序資訊就可以確定是否為同樣內容的樹。因此可以從treeA 的葉節點進行資訊的建立,把每次要回傳的資料整理成一個前+中的序列跟treeB進行比較,改善後就不需要每次都去遞迴呼叫比對函數,直接對陣列進行比較這樣時間複雜度可以改善到O(n)
🦁 : 恩恩好的那可否請你分別實作剛剛那兩種算法
🐵 : coding...
```
## **219. Contains Duplicate II**
- ***Ref***
- https://www.youtube.com/watch?v=ypn0aZ0nrL4
* **Description** : 給定一個陣列 $arr$ 及一個整數 $k$,檢查是否存在一組 $\begin{equation*}arr[i] = arr[j]\end{equation*}$,且滿足 $\lvert{i-j}\rvert \le k$ 。
- ***Hash map***
* **Solution**
利用 **hashmap** 做輔助,檢查$arr$,從$i=0...n-1$ 依序
1. $arr[i]$ 不在 **hashmap** 中,存入鍵值對 $(key, value) = (arr[i], i)$
2. 如果$arr[i]$ 曾經存在於 **hashmap**,就比對 $i$ 跟 $j=hashmap[arr[i]]$,$\lvert{i-j}\rvert \le {k}$ 是否成立
- 成立,即結束程式 true
- 否則,更新hashmap中$hashmap[arr[i]]$ 的 $value$,回到$step(1)$
```python
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
hashmap = dict()
for i in range(0,len(nums)):
if nums[i] not in hashmap :
hashmap[nums[i]] = i
continue
if i - hashmap[nums[i]] <=k:
return True
hashmap[nums[i]] = i
return False
```
* **Complexity Analysis**
* 時間複雜度 : 每個$arr$中的 $element$ 都看過一次 $\implies O(n)$
* 空間複雜度 : **worst case** 有可能出現每個元素都不一樣,**hashmap**中需要存入全部$element \implies O(n)$
- ***Sliding window***
* **Solution**
從hashmap引入sliding window概念改進,只需要利用一個集合($W$)就可以達成,透過限制集合大小 $\lvert{W}\rvert \le k$, 每次檢查 $arr[i] \in W$ 是否成立。具體作法如以下步驟 :
- 先初始化 $left$ = 0,利用 $right$ 每次遞增檢查
1. 先檢查right - left 是否有超出$k$ (限制$\lvert{W}\rvert \le k$)
* 若超出,就先移除最左邊的$arr[left]$,$left + 1$ 將 window 左方縮減
2. 檢查 $nums[right]$ 是否在 window 中
* 若有,就結束程式
* 若無,$right+1$ 往後檢查,回到 $step(1)$
```python
class Solution:
def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:
window = set()
left = 0
for right in range(0,len(nums)) :
if right - left > k :
window.remove(nums[left])
left += 1
if nums[right] in window :
return True
else :
window.add(nums[right])
return False
```
* **Complexity Analysis**
* 時間複雜度 : $O(n)$
* 空間複雜度 : $O(k)$
```
對答草稿
🦁 : interviewer
🐵 : interviewee
//revised by Iris
🦁 : Hey monkey ,I'm tiger and I'm senior software engineer at MATASpace, In this interview you're going to use a whiteboard to show your idea and implement it by notepad or writing down ur code in IDE but don't compile& run it.
🐵 : ok, i got it
🦁 : Are u ready? Let's start with the first question! Here is the problem, you're given an integer array and integer, return true if there are two distinct indices i and j in the array such that nums[i] == nums[j] and abs(i-j) less than or equal k.
Just a reminder, First, the length of the array is between 1 to 10 to the power of 5. Second, the value in array is between negative 10 to power of 9 to positive 10 to power of 9.
Third, k is between 1 to 10 to power of 5.
Do you get it ? Feel free to ask any questions, if u don’t understand the problem.
🐵 : mmm... if a number in this array, the number would appear more than 2 times or not? or an arbitrarily just shows up?
🦁 : there's no limitation for times of number appearance
🐵 : OK! I just came up with a solution by using the hashmap method, here is the detail below.
By implementing a hashmap to check whether the num appeared before and also record the number with the index.
First, check the array from index equals to 0 to n-1,
* if arr[i] is not in hashmap means it didn't appear before :
we just saved the (num, index) as (key, value) to hashmap
* else arr[i] is existed in hashmap :
checking index = j for the arr[i] saved in hashmap , if abs(i - j) is less than equal to k which means we found a matching pair for the problem.
if we search the whole arr but didn't find any matching pair, then return to false.
the time complexity of this approach is big O of n and space complexity is also big O of n
🦁 : ok, the approach is feasible. but could you improve the space complexity for this approach?
🐵 : yeah, we can imporve this approach by introducing "sliding window" concept. By using a set(W) to save the arr[i] , and left and right pointer to check the window size wouldn't exceed the k. Every time before we check the number[right] , we calculate the right minus left less than or equal to k, if not, we remove the arr[left] from set(W) and shift the left pointer to right by 1, and checking whether or not the arr[right] in the set(W), if there's an arr[right] in set, which means we found a pair matching the problem.
the space complexity can downsize to k, and time complexity remains the same.
🦁 : Great! Could you implement the approach by using the IDE and giving some test samples to validate your idea ?
🐵 : yes, i try it now, (coding ...)
```
## **55. Jump Game**
- **Ref**
- https://www.youtube.com/watch?v=Yan0cv2cLy8&t=325s
* **Description** : 給定一串陣列$arr$,長度為$n$。首位置索引為 $0$,每個$arr[i]$代表從當前位置所能前進的步數,請給定一個算法判斷一個陣列是否能從 $i=0$ $\overset{走到}{\implies}$ $i = n-1$
- ***Brutal force***
* **Solution**
根據以下圖片範例,利用DFS的方式搜索全部可能的結果
1. 過程中,有**任一個結果可到達** $i = n-1{\implies} True$
2. 搜完全部可能性後仍**沒辦法**到達 $i = n - 1 {\implies} False$

* **Complexity Analysis**
* 時間複雜度 : $O(n^{n})$
* 空間複雜度 : $O(n)$
* **Improvement**
* 利用額外一維陣列,資料類型為布林值,進行cache。紀錄不可能到達終點的index為false,每次都去hashmap確認,改善重覆巡訪的overhead
* 時間複雜度 : 可縮減到$O(n^2)$
* 空間複雜度 : $O(n)$
- ***Dynamic Programming***
* **Solution**
從 $i = 0$ 開始,
1. 如果發現 $i > arrival$ 代表從 $i - 1$ 以前的位置都不可能跳到 $i \implies false$
2. 維護 $arrival$ 最大值 $arrival = max(arrival, i + nums[i])$
3. 整個過程中如果 $arrival>n-1 \implies true$
```python
class Solution:
def canJump(self, nums: List[int]) -> bool:
n = len(nums)
arrival = 0
i = 0
while i < n :
if i > arrival :
return False
arrival = max(arrival,i+nums[i])
if arrival >= n-1 :
return True
else:
i += 1
```
* **Complexity Analysis**
- 時間複雜度為 $O(n)$
- 空間複雜度 : 每次維護最遠能走到的距離(arrival)$\implies$ $O(1)$
- ***Greedy***
* **Solution**
$goal$ 先初始化為 $n-1$
1. 從倒數第二位$i = n-2$開始往前看
* 如果$i + arr[i] \ge goal$ 成立,$goal = i$ ,將$i = i-1$重複$step(1)$
2. 當全部看完後檢查$goal$
* $\begin{equation}goal = 0\end{equation} \implies true$
* $\begin{equation}goal \ne 0\end{equation} \implies false$
```python
class Solution:
def canJump(self, nums: List[int]) -> bool:
goal = len(nums)-1
for i in range(len(nums)-2, -1, -1):
if i + nums[i] >= goal :
goal = i
if (not goal) :
return True
else :
return False
```
* **Complexity Analysis**
* 時間複雜度 : $O(n)$
* 空間複雜度 : $O(1)$
* $greedy$ 法在 $best$ $case$ 會略輸給$DP$解法
```
對答草稿
🦁 : interviewer
🐵 : interviewee
🦁 : 接下來是一題難度中等的題目,內容是關於陣列,請你看一下這個陣列,給定一個陣列,陣列中每個數目代表從目前索引所能往前走的最遠距離。請給定一個算法判斷當起始點在索引為0時,是否能成功從0->n-1。
另外注意 1. 陣列的長度落在1~10^4 之間 2. 陣列中的值落在0~10^5
那題目就如上述,你有別的問題嗎?
🐵 : 陣列中的值有規定每次跳躍都要用完嗎,舉例來說 nums[5] = 3 ,是否代表當在index=5,一定要跳三個,還是可以任意從1~3選擇一個進行跳躍?
🦁 : 以你舉例來說,當值落在1~3之間,跳躍的距離可以從1~3之間任意選擇
🐵 : 好的,第一個想法是利用dfs做搜索,設想以下範例,nums[0] 可以跳三個,所以就有 1~ 三條路分別走到 index = 1 ,2 ,3 的可能,以此類推 ,當有一條路可以走到 n-1 就return true,否則就是false。此方法的時間複雜度為 O(n^n) 空間複雜度 O(n) 去對arr 做pop&push。
🦁 : 好的,但這樣的時間複雜度似乎有點高,是否能提出改善的想法或是別的方案呢?
🐵 : 如果是根據上面的想法,可以先利用cache,把不可能到達終點的index紀錄下來這樣後續走到此index時就不用在往下搜索。因為每個index最多就是看後面的index 1+2+3+...+n-1
時間複雜度可以減少到 O(n^2),空間仍維持相同的O(n)
🦁 : 好的,這個改善方案是可行的,但有沒有更好的算法可以減少複雜度到線性時間內呢?
🐵 : 恩... 這題應該還可以利用動態規劃,可以維護一個arrival代表目前所能到達的最遠距離,每次往下一個索引,先檢查前一次更新的arrival是否能到達這裡,如果有,就可以進行arrival更新,否則就代表不可能跳到這個位置,return false。一直持續更新下去到n-1 都成立結束迴圈,就代表可以成功走完整個陣列 => true
🦁 : 好的,那還有沒有別的更好的解法,否則請你對目前最佳的算法進行實作一次
```
---
## 第二次作業-他評 01
### interviewer
- [ ] 優點
* 直接畫圖給面試者看,好心的面試官
* 面試氛圍輕鬆,用畫的給人一種自由發揮的感覺
- [ ] 可改進的地方
* 可用實例來包裝問題,讓問題往實際層面延伸,測試面試者在面對實際情況時如何將其化為簡單的演算法問題
### interviewee
- [ ] 優點
* 口齒清晰,且流程簡潔俐落,可以編寫程式碼邊講解,帶領面試官了解其思考
* 即使REACTO的流程順序不太對但解說連貫不突兀所以無傷大雅
- [ ] 可改進的地方
* leetcode解題記錄不要露出來,因為面試一定不會想考你已經做過的題目,就算你做過也要假裝沒做過
## 第二次作業-他評 02
### interviewer
* 用畫圖的方式讓人更好理解題目要求
* 講解清晰易懂
### interviewee
* 講解清晰
* 在和interviewer溝通時間複雜度考慮較全面,有討論worst case
- [ ] 可改進之處
* 語速有時會偏快,稍微聽不清楚
## 第二次作業-他評 03
### interviewer
- [ ] 優點
* 利用圖來講解題目,使面試過程更為順暢
- [ ] 可改進之處
* 語速有些快
* 可以先讓 interviewee 實作第一個方法的程式碼在詢問改進方案,讓 interviewee 能先透過 coding 找出當前方法的不足之處,可以更全面的對方法做改進。
### interviewee
- [ ] 優點
* 邏輯清晰,在講解方法的實作與時間複雜度分析能用繪圖來清楚說明
- [ ] 可改進之處
* 在作答前有先將疑問提出,確認條件。
* 講話清楚,與 interviewer 有良好的互動
## 第二次作業-他評 04
### interviewer
- [ ] 優點
* 特別畫圖出來給面試者,這肯定是個好人
- [ ] 可改進之處
* 有看到面試官都提邊界範圍了,那就可以針對該部分發問多一些問題
* 面試官可以問,如果嘗試更改邊界範圍,那麼該程式有需要更動嗎?以此測驗出面試者的反應程度。
### interviewee
- [ ] 優點
* 過程相當流暢,邊打程式碼,邊講解
- [ ] 可改進之處
* [2:35](https://youtu.be/PnLzfA89Bu8?si=lA3PF5jwXT75VB9j&t=537): 面試官說明了題目邊界範圍後,然後在面試者後面的方法說明中,並沒有說到對於該邊界範圍你的方法是否能有效應對,可以將該處補上
* 在你用手寫說明時,你的手寫文字看了很痛苦,可以慢慢寫好看一點,相信這對面試有幫助
## 第二次作業-他評 06
### interviewer
- [ ] 優點
* 使用畫圖的方式輔助tree的介紹,讓人很容易就聽懂問題是什麼
* 有模擬出遠端面試那種在等待對方講完話的時間差
- [ ] 可改進之處
* 因為滑鼠作圖較困難,所以多少會比較潦草一點
* 語速有時候會過快
### interviewee
- [ ] 優點
* 思考的很全面,在回答前會先將題目問清楚
- [ ] 可改進之處
* 如果是文字的話,或許可以考慮用打字的,會比較快速且清楚
## 第二次作業-他評 07
- [ ] 優點
* 有畫圖解釋,讓人容易理解題目在幹嘛
* 過程相當流暢
- [ ] 可改進之處
* 用滑鼠寫國字的地方改成用打字的會比較好。