diff --git a/package.json b/package.json index a565508..4295282 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "server": "hexo server" }, "hexo": { - "version": "6.3.0" + "version": "7.1.1" }, "dependencies": { "hexo": "^7.1.1", @@ -29,4 +29,4 @@ "hexo-theme-landscape": "^1.0.0", "hexo-wordcount": "^6.0.1" } -} +} \ No newline at end of file diff --git a/source/_drafts/dsa-tutorial.md b/source/_drafts/dsa-tutorial.md new file mode 100644 index 0000000..03e2c47 --- /dev/null +++ b/source/_drafts/dsa-tutorial.md @@ -0,0 +1,688 @@ +--- +title: 資料結構與演算法 +categories: + - +tags: + - +cover: /img/cover/C++.png +date: +--- + +# 演算法與資料結構入門 +本文所有內容與資料皆由本人蒐集與撰寫,轉載請註明出處。 +本篇講義尚未完成。 + +![](https://hackmd.io/_uploads/Sy7FTNHea.png) + +# 入門介紹 +- [甚麼是演算法?](https://jason-chen-1992.weebly.com/home/-whats-algorithm) +- [甚麼是資料結構?](https://hackmd.io/@Aquamay/H1nxBOLcO/https%3A%2F%2Fhackmd.io%2F%40Aquamay%2Frk1C8ni5d) + +來複習一下學習地圖,以臺大資工系必修為例: +![](https://i.imgur.com/Y2AdRO3.png =80%x) +以臺大資管系必修為例: +![](https://i.imgur.com/aC9lfrs.png =70%x) + +而在演算法與資料結構的世界裡面,大概又可以分為以下幾種類別。在我們之後的課程中,會逐漸的介紹每個名詞以及其對應概念: +![image](https://hackmd.io/_uploads/BkmCHf7UR.png) + + + +# 競賽相關 +適合國 / 高中生參加的相關競賽有以下: +- [Bebras 國際運算思維挑戰(簡單)](https://bebras.csie.ntnu.edu.tw/) +- [APCS(個人)](https://apcs.csie.ntnu.edu.tw/) +- [ISSC(團體)](https://issc.csroc.org.tw/) +- [TOI(資訊奧林匹亞,較難)](https://tpmso.org/toi/) + +建議可以向學校老師或相關社團與補習班詢問更多細節。 + +### APCS Practice +- [應試相關資訊](https://apcs.csie.ntnu.edu.tw/index.php/info/) + - 建議每項都要讀過,特別是環境 / 作答系統 / 考場規則 +#### 實作題 +- [2024/06 APCS](https://zerojudge.tw/Problems?tag=2024%E5%B9%B46%E6%9C%88) and [Solution](https://hackmd.io/cn9ndy19RbKHOJKBRx4aEA) +- [2024/01 APCS](https://zerojudge.tw/Problems?tag=2024%E5%B9%B41%E6%9C%88) and [Solution](https://hackmd.io/@bangyewu/SkKxG8Oua) +- [2023/10 APCS](https://zerojudge.tw/Problems?tag=2023%E5%B9%B410%E6%9C%88) and [Solution](https://hackmd.io/@bangyewu/SkxXo4QGa) or [Solution 2](https://dada878.com/blogs/apcs-2023-10-solution) +- [2023/06 APCS](https://zerojudge.tw/Problems?tag=2023%E5%B9%B46%E6%9C%88) and [Solution](https://hackmd.io/@bangyewu/B13lefwMp) +#### 觀念題 +- [APCS 觀念題分析系列](https://hackmd.io/@howkii-studio/apcs_overview/https%3A%2F%2Fhackmd.io%2F%40howkii-studio%2Fapcs_exercise_cs) +- [APCS 觀念題資源分享](https://hackmd.io/@apcser/B1N5GYEto) + +#### 相關資源 +- 推薦:[AP325 - 從 APCS 實作題檢測三級到五級(C++)](https://drive.google.com/file/d/1hX7q3wVKkLuoMhEEm7uuLjq4BuhZAEgn/view?usp=drive_link) + - [AP325(Python)](https://hackmd.io/@bangyewu/Hy2kbYLI6/%2Fg2kqHh5_Q4eQnz-mfNu3Kw) +- [FB Group - APCS 實作題檢測](https://www.facebook.com/groups/359446638362710/) +- [PyAp45 Playlist - Python on APCS 四五級分](https://www.youtube.com/playlist?list=PLpmg1QLbgMuRQXHRkX9iDHyAVIW1D6OJF) +- [How to master DSA](https://blog.algomaster.io/p/how-i-mastered-data-structures-and-algorithms) + +# 排序演算法(Sorting Algorithms) +![image](https://hackmd.io/_uploads/Hk0Xhj8x0.png) + +排序演算法是一種將一組資料按照特定規則重新排列的方法。 + +常見的排序方法有: +- 選擇排序法(Selection Sort) +每次挑最大或最小的,依序移動到對應的位置。 +- 插入排序法(Insertion Sort) +照順序每次拿一個,並插入正確的位置。 +- 氣泡排序法(Bubble Sort) +就像有個氣泡,兩兩比對後留下大的,每次都把最大的帶到最後面。 +- 合併排序法(Merge Sort) +將陣列不斷細分,再將細分後的結果兩兩合併。 +- 快速排序法(Quick Sort) +選定一個 Pivot,將比他小的丟左邊比他大的丟右邊,再針對左右兩部分進行一次相同的事情。 + +練習:試著實作以上五種常見的 Sorting 方法吧! + +> **推薦參考**:[介紹排序演算法的網站](http://notepad.yehyeh.net/Content/Algorithm/Sort/Sort.php) +> **推薦參考**:[寫程式的基本功:排序演算法](https://magiclen.org/sorting-algorithm/) +> 補充:[Python 中的 sorting](https://officeguide.cc/python-sort-sorted-tutorial-examples/)、[C++ 中的 sorting](https://shengyu7697.github.io/std-sort/) +> 補充:[最貼近現實的排序演算法 - Timsort](https://jason18153.medium.com/%E6%9C%80%E8%B2%BC%E8%BF%91%E7%8F%BE%E5%AF%A6%E7%9A%84%E6%8E%92%E5%BA%8F%E6%BC%94%E7%AE%97%E6%B3%95-timsort-a75da75b65a2) + +補充:還有一些很奇怪的排序方法,看了會覺得很傻眼又好笑。如果有興趣可以參考 [10 Forbidden Sorting Algorithms](https://www.youtube.com/watch?v=ktgxMtWMflU) ,舉幾個例子像是: +- Bogo 排序法(Bogo Sort) +一直隨機重新排序,直到剛好符合大小順序。(猴子打字機定理) +- 史達林排序法(Stalin's Sort) +遇到大小順序不對的就直接刪掉。(所以最後的 list 有可能比原本的短) + +# 搜尋演算法(Search Algorithms) +![image](https://hackmd.io/_uploads/HyNn3iIl0.png) + +搜尋演算法是一種用來在資料集中尋找特定項目的方法或步驟。 + +## 適用於未排序或已排序的資料 +### 線性搜尋(Linear Search) +直接一項一項搜尋,直到找到要的東西為止。 +最基本的搜尋方式,時間複雜度為 $O(N)$。 + +## 僅適用於排序的資料 +以下的搜尋演算法,會利用資料**已經排序過**的性質,來加速搜尋的過程。 + +### 二分搜尋(Binary Search) +很重要的搜尋演算法!將資料中間的與目標相比,若目標較大則往右再做一次搜尋,目標較小則往左,直到找到為止,可以參考 [二分搜尋法教學](https://magiclen.org/binary-search/) 與 [二分搜尋法的一些細節](https://medium.com/appworks-school/binary-search-%E9%82%A3%E4%BA%9B%E8%97%8F%E5%9C%A8%E7%B4%B0%E7%AF%80%E8%A3%A1%E7%9A%84%E9%AD%94%E9%AC%BC-%E4%B8%80-%E5%9F%BA%E7%A4%8E%E4%BB%8B%E7%B4%B9-dd2cd804aee1)。 +#### Leetcode: +- [35. Search Insert Position](https://leetcode.com/problems/search-insert-position/) +- [69. Sqrt(x)](https://leetcode.com/problems/sqrtx/) +- [278. First Bad Version](https://leetcode.com/problems/first-bad-version) + +### 指數搜尋(Exponential Search) +與二分搜尋類似,只是使用 2^N 作為搜尋的 index。 +### 插補搜尋(Interpolation Search) +用線性插值的概念,來加速尋找的過程。 + +> **推薦參考**:[寫程式的基本功:搜尋演算法](https://magiclen.org/search-algorithm/) + + +# 時間複雜度(Time Complexity) +時間複雜度是衡量演算法執行效率的一個重要指標,表示隨著輸入規模增加,算法執行所需時間的增長速度。 + +常用的符號有: +- O-Notation:Big-O(Worst Case,最差) +- Θ-Notation:Big-Theta(Average Case,平均) +- Ω−Notation:Big-Omega(Best Case,最佳) + +一般來說,我們最關心的是最差狀況下的時間複雜度(Big-O),關於詳細的數學推導可以參見補充。一些常見的時間複雜度與其例子為: +* $O(1)$:陣列讀取 +* $O(n)$:簡易搜尋 +* $O(log n)$:二分搜尋 +* $O(nlogn)$:合併排序 +* $O(n^2)$:選擇排序 +* $O(2^n)$:費波那契數列(遞迴版本) + +![](https://hackmd.io/_uploads/rk2-JzHlp.png) + + +我們來看看這些例子是如何被計算出來的: +- [初學者學演算法|從時間複雜度認識常見演算法](https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E5%BE%9E%E6%99%82%E9%96%93%E8%A4%87%E9%9B%9C%E5%BA%A6%E8%AA%8D%E8%AD%98%E5%B8%B8%E8%A6%8B%E6%BC%94%E7%AE%97%E6%B3%95-%E4%B8%80-b46fece65ba5) +- [初學者學演算法|排序法進階:合併排序法](https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E6%8E%92%E5%BA%8F%E6%B3%95%E9%80%B2%E9%9A%8E-%E5%90%88%E4%BD%B5%E6%8E%92%E5%BA%8F%E6%B3%95-6252651c6f7e) +- [初學者學演算法|從費氏數列認識何謂遞迴](https://medium.com/appworks-school/%E5%88%9D%E5%AD%B8%E8%80%85%E5%AD%B8%E6%BC%94%E7%AE%97%E6%B3%95-%E5%BE%9E%E8%B2%BB%E6%B0%8F%E6%95%B8%E5%88%97%E8%AA%8D%E8%AD%98%E4%BD%95%E8%AC%82%E9%81%9E%E8%BF%B4-dea15d2808a3) + + +> 補充:[複雜度與漸進符號之數學](http://alrightchiu.github.io/SecondRound/complexityasymptotic-notationjian-jin-fu-hao.html) +> 補充:[時間複雜度與空間複雜度](https://jason-chen-1992.weebly.com/home/time-space-complexity) + + +# 資料結構(Data Structure) +資料結構是一種設計、組織、儲存資料的方式,以實現最佳性能和效率。這些結構包含不同形式,像是陣列、鍊結列表、樹、圖等等。選擇適當的資料結構對於解決特定的問題至關重要,不同的資料結構可以用於不同的應用,並且可以極大地影響程序的運行時間和記憶體使用。 + +相關參考: +- [C++ STL 容器(一) - 基本介紹](https://jasonblog.github.io/note/c++/stl_rong_qi_4e0029_-_ji_ben_jie_shao.html) +- [C++ API / STL 整理](https://hackmd.io/@meyr543/BkgMaiV6Y) +- [演算法與資料結構](https://alrightchiu.github.io/SecondRound/mu-lu-yan-suan-fa-yu-zi-liao-jie-gou.html) + +## 陣列(Array) +![](https://hackmd.io/_uploads/SJJ_pSKMa.png) +可隨機存取的一串連續記憶體位址。 +- [陣列 (Array) 簡介](https://notfalse.net/15/array-intro) +> 補充:[List Are Arrays in Python](https://michaeliscoding.com/lists-are-arrays-in-python/) +### Leetcode +- [26. Remove Duplicates from Sorted Array](https://leetcode.com/problems/remove-duplicates-from-sorted-array/) +- [27. Remove Element](https://leetcode.com/problems/remove-element/description/) + +## 鏈結串列(Linked List) +![](https://hackmd.io/_uploads/HkbKTHFG6.png) +元素間彼此串聯在一起,形成一條鍊子的資料結構。 +![image](https://hackmd.io/_uploads/SkxzAOjQp.png) +也可以是上圖雙向連結的形式。 + +- [Linked List:Intro(簡介)](https://alrightchiu.github.io/SecondRound/linked-list-introjian-jie.html) +- [Linked List:新增資料、刪除資料、反轉](https://alrightchiu.github.io/SecondRound/linked-list-xin-zeng-zi-liao-shan-chu-zi-liao-fan-zhuan.html) + +### Leetcode +- [21. Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/) +- [24. Swap Nodes in Pairs](https://leetcode.com/problems/swap-nodes-in-pairs/) +- [206. Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/) + +## 堆疊(Stack, Last-In-First-Out, LIFO) +![](https://hackmd.io/_uploads/BkdcTSFMp.png) +單向進出,後進先出的資料結構,如同把東西堆疊起來。 +- [Stack: Intro(簡介)](https://alrightchiu.github.io/SecondRound/stack-introjian-jie.html) +- [Stack: 以Array與Linked list實作](https://alrightchiu.github.io/SecondRound/stack-yi-arrayyu-linked-listshi-zuo.html) +- [Special Application: Min Stack](https://alrightchiu.github.io/SecondRound/stack-neng-gou-zai-o1qu-de-zui-xiao-zhi-de-minstack.html) + +## 佇列(Queue, First-In-First-Out, FIFO) +![](https://hackmd.io/_uploads/H1tjpHFM6.png) +單向進出,先進先出的資料結構,如同在排隊一般。 + +- [Queue: Intro(簡介),並以Linked list實作](https://alrightchiu.github.io/SecondRound/queue-introjian-jie-bing-yi-linked-listshi-zuo.html) +- [Queue: 以Array實作Queue](https://alrightchiu.github.io/SecondRound/queue-yi-arrayshi-zuo-queue.html) + +## Stack & Queue in Python: Collections.deque() +Deque(發音類似 Deck)為 Double-Ended Queue 的縮寫,顧名思義,Deque 是一個支援雙向存取的 Queue,也就是說,你可以從頭跟尾插入或移除元素。 + - [官方說明文件](https://docs.python.org/zh-tw/3/library/collections.html#collections.deque) + +```python= +from collections import deque +D = deque([1,2,3]) +D.append(4) +D.appendleft(0) +print(D[2], D) +print(D.pop(), D) +print(D.popleft(), D) +``` +``` +Output: +2 deque([0,1,2,3,4]) +4 deque([0,1,2,3]) +0 deque([1,2,3]) +``` +Q:那他與 Stack 跟 Queue 有甚麼關係呢? +> A: +> Stack: 只使用 D.append() 與 D.pop() 的 deque +> Queue: 只使用 D.append() 與 D.popleft() 的 deque +> +> 其實只要符合 Stack 跟 Queue 的性質都可以。 +> 因此以下使用方式也對,但較不建議使用: +> Stack: 只使用 D.appendleft() 與 D.popleft() 的 deque +> Queue: 只使用 D.appendleft() 與 D.pop() 的 deque + +以下為使用 collections.deque 來實作 stack 與 queue 的例子: +```python= +from collections import deque + +# For Stack, you can only use the following operations +S = deque() +# push from a stack: +S.append(val) +# pop from a stack: +val = S.pop() +# peek from a stack: +top = S[-1] +# check size of a stack: +size = len(S) +# check stack is empty: +isEmpty = (size == 0) + +# ========================================= +# For Queue, you can only use the following operations +Q = deque() +# push from a queue: +Q.append(val) +# pop from a queue: +val = Q.popleft() +# peek from a queue: +top = Q[0] +# check size of a queue: +size = len(Q) +# check queue is empty: +isEmpty = (size == 0) +``` +另外也可以參考 Python 官方對 [將 List 作為 Stack / Queue 使用](https://docs.python.org/zh-tw/3/tutorial/datastructures.html#using-lists-as-stacks) 的說明。 + +介紹完 collections.deque() 以後,讓我們來實際練習一下 Leetcode 的題目吧! + +### Leetcode +#### Stack +- [20. Valid Parentheses](https://leetcode.com/problems/valid-parentheses/) +- [155. Min Stack](https://leetcode.com/problems/min-stack/description/) +- [232. Implement Queue using Stacks](https://leetcode.com/problems/implement-queue-using-stacks/) + +#### Queue +- [225. Implement Stack using Queues](https://leetcode.com/problems/implement-stack-using-queues/) +- [341. Flatten Nested List Iterator](https://leetcode.com/problems/flatten-nested-list-iterator/) +- [387. First Unique Character in a String](https://leetcode.com/problems/first-unique-character-in-a-string/) + + +## 樹(Tree) +![](https://hackmd.io/_uploads/S13npBFMp.png) +基本單位為 Node,且只有一個 Node 是 Root(無人指向的 Node),且不存在任何 Cycle。每個 Node 可以指向多個 Child。 +- [Tree(樹): Intro(簡介)](https://alrightchiu.github.io/SecondRound/treeshu-introjian-jie.html) + +### 二元樹(Binary Tree) +![image](https://hackmd.io/_uploads/SJQuAds7a.png) +當樹中的每個 Node 都只有兩個 Child,即為 Binary Tree。 + +- [Binary Tree: Intro(簡介)](https://alrightchiu.github.io/SecondRound/binary-tree-introjian-jie.html) +- [Binary Tree: Traversal(尋訪)](https://alrightchiu.github.io/SecondRound/binary-tree-traversalxun-fang.html) + 以上面的 Binary Tree 為例: + - Pre-Order Traversal: ABDECFG + - In-Order Traversal: DBEAFCG + - Post-Order Traversal: DEBFGCA + ![image](https://hackmd.io/_uploads/rJEw81ajp.png) +> 延伸閱讀:[Binary Tree: 建立一棵Binary Tree](https://alrightchiu.github.io/SecondRound/binary-tree-jian-li-yi-ke-binary-tree.html) + + +### 二元搜尋樹(Binary Search Tree) +![image](https://hackmd.io/_uploads/rJ5d1tom6.png) +若一個二元樹中,所有 Node 都滿足 Node.left.value < Node.value < Node.right.value,就稱為二元搜尋樹。二元搜尋樹有良好的排序特質,可以幫助我們找到想要的資料。 +- [Binary Search Tree: Intro(簡介)](https://alrightchiu.github.io/SecondRound/binary-search-tree-introjian-jie.html) +- [Binary Search Tree: Search(搜尋資料)、Insert(新增資料)](https://alrightchiu.github.io/SecondRound/binary-search-tree-searchsou-xun-zi-liao-insertxin-zeng-zi-liao.html) +- [Binary Search Tree: Sort(排序)、Delete(刪除資料)](https://alrightchiu.github.io/SecondRound/binary-search-tree-sortpai-xu-deleteshan-chu-zi-liao.html) +> 注意:在 Binary Search Tree 上做 In-Order Traversal,即可得到排序好的資料! + +### 進階 - 紅黑樹(Red Black Tree) +紅黑樹是一種進階的 BST(Binary Search Tree),透過將每個節點塗上紅色或黑色,並在新增或刪除資料時進行適當的結構調整(旋轉子樹),可以維持 BST 的高度不會相差太多,進而在搜尋時能有較好的效率。細節太過複雜這邊暫時跳過,之後有時間 or 有興趣再回來講。 + +![image](https://hackmd.io/_uploads/HyclMKj7p.png) +- [Red Black Tree: Intro(簡介)](https://alrightchiu.github.io/SecondRound/red-black-tree-introjian-jie.html) +> 延伸閱讀: +> - [Red Black Tree: Rotation(旋轉)](https://alrightchiu.github.io/SecondRound/red-black-tree-rotationxuan-zhuan.html) +> - [Red Black Tree: Insert(新增資料)與Fixup(修正)](https://alrightchiu.github.io/SecondRound/red-black-tree-insertxin-zeng-zi-liao-yu-fixupxiu-zheng.html) +> - [Red Black Tree: Delete(刪除資料)與Fixup(修正)](https://alrightchiu.github.io/SecondRound/red-black-tree-deleteshan-chu-zi-liao-yu-fixupxiu-zheng.html) + +### 進階 - AVL 樹(AVL Tree) +![image](https://hackmd.io/_uploads/r1GAdTXt6.png) +AVL Tree 是一種 Binary search tree 實做方式,大部分的實做方式與 BST 一樣,差異在於 AVL tree 在過程中會透過計算並調整樹的結構來讓樹維持平衡,而不會導致 BST 過度傾斜(不平衡),與紅黑樹的目標類似。細節太過複雜這邊暫時跳過,之後有時間 or 有興趣再回來講。 + +> 延伸閱讀:[資料結構與演算法:AVL Tree](https://josephjsf2.github.io/data/structure/and/algorithm/2019/06/22/avl-tree.html) + +### Leetcode +#### Basic - Traversal & Search +Please try to use both recursion and stack/queue to solve problem 102 and 144. (Hint: For 102(Level-Order) you should use **Queue**, for 144(Pre-Order) you should use **Stack**) +- [94. Binary Tree Inorder Traversal](https://leetcode.com/problems/binary-tree-inorder-traversal/description/) +- [102. Binary Tree Level Order Traversal](https://leetcode.com/problems/binary-tree-level-order-traversal/description/) +- [144. Binary Tree Preorder Traversal](https://leetcode.com/problems/binary-tree-preorder-traversal/description/) +- [145. Binary Tree Postorder Traversal](https://leetcode.com/problems/binary-tree-postorder-traversal/description/) +- [700. Search in A Binary Search Tree](https://leetcode.com/problems/search-in-a-binary-search-tree/description/) + +#### Applications +- [100. Same Tree](https://leetcode.com/problems/same-tree/description/) +- [226. Invert Binary Tree](https://leetcode.com/problems/invert-binary-tree/description/) +- [530. Minimum Absolute Difference in BST](https://leetcode.com/problems/minimum-absolute-difference-in-bst/description/) +- [1026. Maximum Difference Between Node and Ancestor](https://leetcode.com/problems/maximum-difference-between-node-and-ancestor/description/) +- [1609. Even Odd Tree](https://leetcode.com/problems/even-odd-tree/description) + +#### Tools +- [BT Visualizer](https://eniac00.github.io/btv/) + +## 圖(Graph) +![](https://hackmd.io/_uploads/S1v-AHFM6.png) +圖比樹的限制更少,給定一群節點與相連的邊,即可稱為圖。邊可以分為有向與無向,節點之間的連結並沒有限制,也因此圖並不像樹有 root、parent、siblings、height 等等屬性,且有可能會有 cycle 的出現。 + +### Graph & Tree 的比較 +![](https://hackmd.io/_uploads/HkvhyNtf6.png) +Tree 可以被看成一種特殊的 Graph,就像 Binary Tree 是一種特殊的 Tree 一樣。 + +### Graph 的表示法 +![image](https://hackmd.io/_uploads/H1otPi8gA.png) + +- [Graph: Intro(簡介)](https://alrightchiu.github.io/SecondRound/graph-introjian-jie.html) + +### Graph 的搜尋 +![image](https://hackmd.io/_uploads/r1Ef-7Pnp.png) +最常見、也最常使用的搜尋方法有兩種:BFS 與 DFS。兩者的差異在於搜尋的優先順序不同,並且分別可以使用 Queue 與 Stack 來實作。 + +#### 廣度優先搜尋(Breadth First Search, BFS) +- [Breadth First Search or BFS for a Graph](https://www.geeksforgeeks.org/breadth-first-search-or-bfs-for-a-graph/) + +#### 深度優先搜尋(Depth First Search, DFS) +- [Depth First Search or DFS for a Graph](https://www.geeksforgeeks.org/depth-first-search-or-dfs-for-a-graph/) + +#### 那 DFS 與 BFS 可以應用在 Tree 上嗎? +因為 Tree 也是 Graph 的一種,所以當然可以,而且非常常用! +> 延伸閱讀:[DFS、BFS 與 Pre-Order、In-Order、Post-Order、Level-Order 的關係](https://stackoverflow.com/a/57598470/15894431) + +### Leetcode +- [133. Clone Graph](https://leetcode.com/problems/clone-graph/description/) +- [797. All Paths From Source to Target](https://leetcode.com/problems/all-paths-from-source-to-target/description/) +- [841. Keys and Rooms](https://leetcode.com/problems/keys-and-rooms/) +- [997. Find the Town Judge](https://leetcode.com/problems/find-the-town-judge/description/) + +--- + +#### 在圖中找環(Cycle) +上面的兩個例子中,都使用 `visited` 來紀錄該節點是否被拜訪過。因此,在**無向圖**中,若我們下一個要拜訪的節點的 `visited` 為 `True`,那我們就可以知道該圖中有環的存在。那在**有向圖**中呢?以下圖來說: +![image](https://hackmd.io/_uploads/rkSj6pq0a.png) +若我們在右邊的有向圖 b 中,用相同的 DFS 來尋找是否有環,假設第一次尋訪的路徑為 (1,2,4,5),而第二次尋訪退回到 2 並往下尋訪 (3,4),我們這時候會發現 `visited[4] == True`,並且判定該圖有環,但實際上是沒有的。那我們該怎麼做呢? + +在往下看之前,先自己想想看喔! + +答案:使用三個不同的值來表示每個節點不同的尋訪狀態(黑、白、灰)。黑色代表已經完成搜尋,白色代表還沒搜尋過,灰色代表正在這條 path 上搜尋,等搜尋完成後就會改為黑色。因此,若我們搜尋時遇到灰色節點,就可以知道該圖存在 cycle! +```python= +# Determine if a directed graph is acyclic +# True: Acyclic; False: Cyclic +graph = ... # adjacency list representation +N = len(graph) +visited = [0] * N +def checkNoCycle(node): + if visited[node] == -1: # gray, currently visiting + return False + elif visited[node] == 1: # black, done visiting + return True + else: # white, not visited yet (visited[node] == 0) + visited[node] = -1 + for neighbor in graph[node]: + if not dfs(neighbor): + return False + visited[node] = 1 + # print(node) + return True +``` + +> 補充:[The purpose of grey node in graph depth-first search](https://cs.stackexchange.com/questions/9676/the-purpose-of-grey-node-in-graph-depth-first-search) + +### 連通分量(Connected Components) +![image](https://hackmd.io/_uploads/r1EGVkc-A.png) +![ConnectedComponent4](https://hackmd.io/_uploads/BkStEJ9WR.png) + + +- [Connected Component - 演算法筆記](https://web.ntnu.edu.tw/~algo/ConnectedComponent.html) +- [Strongly Connected Components - Kosaraju 演算法](https://www.cnblogs.com/RioTian/p/14026585.html) + +> 補充:[Connected Components in an Undirected Graph](https://www.geeksforgeeks.org/connected-components-in-an-undirected-graph/) +> 補充:[Strongly Connected Components](https://www.geeksforgeeks.org/strongly-connected-components/) +> 補充:[Tarjan 演算法與 Kosaraju 演算法](https://hackmd.io/@erichung0906/HkjZBH_IK) + +### Leetcode +- [207. Course Schedule](https://leetcode.com/problems/course-schedule/description/) +- [1971. Find if Path Exists in Graph](https://leetcode.com/problems/find-if-path-exists-in-graph/description/) + + +> 延伸閱讀: +> - [Graph: Breadth-First Search(BFS,廣度優先搜尋)](https://alrightchiu.github.io/SecondRound/graph-breadth-first-searchbfsguang-du-you-xian-sou-xun.html) +> - [Graph: Depth-First Search(DFS,深度優先搜尋)](https://alrightchiu.github.io/SecondRound/graph-depth-first-searchdfsshen-du-you-xian-sou-xun.html) +> - [Graph: 利用DFS和BFS尋找Connected Component](https://alrightchiu.github.io/SecondRound/graph-li-yong-dfshe-bfsxun-zhao-connected-component.html) +> - [Graph: 利用DFS尋找Strongly Connected Component(SCC)](https://alrightchiu.github.io/SecondRound/graph-li-yong-dfsxun-zhao-strongly-connected-componentscc.html) +> - [Graph: 利用DFS尋找DAG的Topological Sort(拓撲排序)](https://alrightchiu.github.io/SecondRound/graph-li-yong-dfsxun-zhao-dagde-topological-sorttuo-pu-pai-xu.html) + +## 矩陣(Matrix) +![image](https://hackmd.io/_uploads/SyxulaZA6.png) + +在介紹 Graph 的表示方法時,我們提到了兩種不同的表示方式,分別是鄰接串列(Adjacency List)與鄰接矩陣(Adjacency Matrix)。不過其實,矩陣本身也可以被當作一種資料結構來使用,舉凡像是迷宮、地圖等等具有 2D 性質的資料型態,都可以使用矩陣來表示。 + +Matrix 的相關操作其實都與 Graph 差不多,不外乎就是在 Matrix 中進行搜尋或遍歷,但因為我們可以以 $O(1)$ 的複雜度,直接取用 Matrix 內部中的任一元素(`matrix[row][col]`),所以 Matrix 在某些應用上具有較為快速的優勢。那接著就讓我們直接實戰演練吧! +### Leetcode +- [200. Number of Islands](https://leetcode.com/problems/number-of-islands/description/) +- [994. Rotting Oranges](https://leetcode.com/problems/rotting-oranges/description/) +- [1351. Count Negative Numbers in a Sorted Matrix](https://leetcode.com/problems/count-negative-numbers-in-a-sorted-matrix/description/) + +## 堆積(Heap) +![](https://hackmd.io/_uploads/BJqGCStzT.png) + +### 堆積排序法(Heap Sort) +- [[演算法(Algorithm)] 堆積排序法(Heap Sort)](https://notepad.yehyeh.net/Content/Algorithm/Sort/Heap/Heap.php) + +對陣列做 Heapify 變成 Max Heap,再不斷把最大值往後擺。 +> 練習:試著實作 Heap Sort 吧! + +## 優先佇列(Priority Queue) +![image](https://hackmd.io/_uploads/S1CWOylZ0.png) + +- [Priority Queue:Intro(簡介)](https://alrightchiu.github.io/SecondRound/priority-queueintrojian-jie.html) +- [Priority Queue:Binary Heap](https://alrightchiu.github.io/SecondRound/priority-queuebinary-heap.html) + +### Stack / Queue v.s. Priority Queue? +Stack 與 Queue 其實可以被視作特殊狀況的 Priority Queue: +- Stack:加入 Priority Queue 的 Priority 是嚴格遞增的 + - 因此最後加入的元素 Priority 最高,會先被丟出 Stack +- Queue:加入 Priority Queue 的 Priority 是嚴格遞減的 + - 因此最先加入的元素 Priority 最高,會先被丟出 Queue + + +### Priority Queue v.s. Heap? +- Priority Queue:一種**抽象資料類別**,著重的點是描述這個資料類別應該具有甚麼性質與操作方法,不直接討論實作方法。 +- Heap:一種**資料結構**,著重的點在以特定的結構儲存資料。 + +因為他們具有類似的性質,所以用 Heap 的結構來實作 Priority Queue 這個資料類別,可以說是非常適合,也因此乍看之下他們好像是一樣的東西。但兩者概念上有些微不同,亦可以用其他方式來實做 Priority Queue。 + +> 補充:[Difference between priority queue and a heap](https://stackoverflow.com/a/18993313/15894431) +> 補充:[What's the difference between heapq and PriorityQueue in python?](https://stackoverflow.com/questions/36991716/whats-the-difference-between-heapq-and-priorityqueue-in-python) + +## Heap / Priority Queue in Python: heapq +- [Python heapq](https://docs.python.org/zh-tw/3.10/library/heapq.html) +```python= +import heapq +k = 3 +H = [9,7,5,3,1] +heapq.heapify(H) +heapq.heappush(H,2) +element = heapq.heappop(H) +element = heapq.heappushpop(H,2) +klargest = heapq.nlargest(k, H) +ksmallest = heapq.nsmallest(k, H) +``` + +### Leetcode +- [703. Kth Largest Element in a Stream](https://leetcode.com/problems/kth-largest-element-in-a-stream/description/) +- [1046. Last Stone Weight](https://leetcode.com/problems/last-stone-weight/description/) +- [1642. Furthest Building You Can Reach](https://leetcode.com/problems/furthest-building-you-can-reach/description/) + +## 雜湊表(Hash Table) +![](https://hackmd.io/_uploads/r1cQCHKMa.png) + +### 雜湊函數的性質 +雜湊函式是一種把輸入數據轉換成固定長度的字符串(雜湊值)的工具。它有以下特點: +* 固定長度輸出:無論輸入數據有多長,輸出都是固定長度。 +* 無法回推:從雜湊值無法反推出原始輸入(即單向性),保證了數據的不可逆性。 +* 抗碰撞性:難以找到兩個不同的輸入有相同的雜湊值。 +* 快速計算:能快速生成雜湊值。 + +![image](https://hackmd.io/_uploads/ByQ3G5S7R.png) +![image](https://hackmd.io/_uploads/S1vaz9S7A.png) +![image](https://hackmd.io/_uploads/ByaTfqrXA.png) + +- [[資料結構] 雜湊 (Hash)](https://ithelp.ithome.com.tw/articles/10208884) + +> Images are from [什麼是Hash Function? 有什麼特性及用途?](https://homuchen.com/posts/what-is-hash-function-its-properties-and-usages/) + +### 雜湊函數的應用 +* 資料完整性驗證:確保收到的資料是完整的,或確保文件未被篡改。 +* 密碼儲存:儲存密碼的雜湊值,而不是明文密碼,提高安全性。常見的加密方式如 MD5、SHA-256 等等,若有興趣可以再研究密碼學。 +* 資料結構:用於實現雜湊表,能夠快速存取資料。使用雜湊函數的常見資料結構包含集合(Set)、字典(Dictionary)等等。 +* 數位簽章:保護資料完整性和驗證身份。 + +> 補充:[雜湊函數 - 維基百科](https://zh.wikipedia.org/zh-tw/%E6%95%A3%E5%88%97%E5%87%BD%E6%95%B8) + +### Leetcode +- [49. Group Anagrams](https://leetcode.com/problems/group-anagrams/description/) +- [242. Valid Anagram](https://leetcode.com/problems/valid-anagram/description/) +- [387. First Unique Character in a String](https://leetcode.com/problems/first-unique-character-in-a-string/description/) +- [2441. Largest Positive Integer That Exists With Its Negative](https://leetcode.com/problems/largest-positive-integer-that-exists-with-its-negative/description/) + +## 並查集(Disjoint Set / Union Find) +![image](https://hackmd.io/_uploads/ry305yeWA.png) +- [Union-Find / Disjoint-Set – 陪你刷題](https://haogroot.com/2021/01/29/union_find-leetcode/) + +一個包含路徑壓縮,並以 rank (size of set) 來作為 union 依據的 Union-Find 資料結構,大概會是如下的形式: + +```python= +class UF: + def __init__(self, N): + self.parent = list(range(N)) + self.size = [1] * N + self.count = N + + def union(self, x, y): + rootX = self.find(x) + rootY = self.find(y) + if rootX != rootY: + self.count -= 1 + if self.size[rootX] < self.size[rootY]: + self.parent[rootX] = rootY + self.size[rootY] += self.size[rootX] + else: + self.parent[rootY] = rootX + self.size[rootX] += self.size[rootY] + + def find(self, x): + while self.parent[x] != x: + self.parent[x] = self.parent[self.parent[x]] + x = self.parent[x] + return x +``` + +### Leetcode +- [547. Number of Provinces](https://leetcode.com/problems/number-of-provinces/description/) +- [1971. Find if Path Exists in Graph](https://leetcode.com/problems/find-if-path-exists-in-graph/description/) + +## 字典樹(Prefix Tree / Trie) +![image](https://hackmd.io/_uploads/BJdBwzXIR.png) +- [Trie(字典樹)](https://hackmd.io/@TienYi/trie) +### Leetcode + +# 其他常見的演算法(Algorithms) +演算法(Algorithm)是在有限的步驟之內,提供明確的法則,求出問題正確答案的程序。它可以是一種方法、法則或者程序,讓資料可按照預先設計的方式處理。 + +- [認識演算法](https://hackmd.io/@howkii-studio/Bkf-2DQiw/https%3A%2F%2Fhackmd.io%2F%40howkii-studio%2Falgorithm) +- 推薦補充閱讀:[演算法筆記](https://web.ntnu.edu.tw/~algo/) + - [Algorithm Design](https://web.ntnu.edu.tw/~algo/AlgorithmDesign.html) + +## 動態規劃(Dynamic Programming) +![image](https://hackmd.io/_uploads/SkqV4HbSR.png) + +**Dynamic Programming = Divide-and-Conquer + Memoization** + +動態規劃是一種解決問題的方法,它將大問題拆解成小問題(Divide-and-Conquer),並記錄每個小問題的解答(Memoization),以便後續使用。這樣做能夠節省計算時間,讓我們更有效地解決問題,針對某些「透過前面答案來計算後面答案的問題」特別有用。 + +- [演算法筆記:DP](https://web.ntnu.edu.tw/~algo/DynamicProgramming.html) + +### Recursion v.s. Dynamic Programming? +我們來看看這題(Leetcode [509. Fibonacci Number](https://leetcode.com/problems/fibonacci-number/description/)): +- 費波納契數列 + 給定輸入整數n,輸出第 n 項費波納契數列 + 舉例:n = 8,輸出為 21 (1 1 2 3 5 8 13 21) + +Recursion 寫法: +```python= +# Recursion +def fibonacci_recursive(n): + if n <= 0: + return 0 + elif n == 1: + return 1 + else: + return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2) +``` + +Dynamic Programming 寫法: +```python= +# Dynamic Programming +def fibonacci_dp(n): + if n <= 0: + return 0 + elif n == 1: + return 1 + else: + fib = [0, 1] + for i in range(2, n + 1): + fib.append(fib[i - 1] + fib[i - 2]) + return fib[n] +``` + +#### 時間複雜度分析 +接著我們來測量一下他們的速度差異: + +```python= +import timeit +print("遞迴方法耗時: %.6f 秒"%(timeit.timeit(lambda: fibonacci_recursive(30),number=1))) +print("動態規劃方法耗時: %.6f 秒"%(timeit.timeit(lambda: fibonacci_dp(30),number=1))) +``` +``` +Output: +遞迴方法耗時: 0.502652 秒 +動態規劃方法耗時: 0.000011 秒 +``` + +為什麼會差這麼多呢?我們來分析看看兩種方法的時間複雜度: + +- 遞迴版本 +時間複雜度:$O(2^n)$。對於每個 fibonacci_recursive(n),它會呼叫 fibonacci_recursive(n-1) 和 fibonacci_recursive(n-2),這種重複計算會導致呼叫函數的次數呈指數級數增長,因此時間複雜度為 $O(2^n)$。 + + +- 動態規劃版本 +時間複雜度:$O(n)$。它只需要計算一次每個費氏數列的值,並將結果存入列表中。迴圈從 2 執行到 n,所以時間複雜度為 $O(n)$。 + + +對於較小的 n,遞迴可能更簡單易懂,並且兩者的速度可能相差不大。但是當 n 較大時,遞迴所需的時間會嚴重惡化,相較之下動態規劃會有更好的效能表現。 + +看到上述兩者的時間複雜度差異之大,是不是發現動態規劃的強大了? + + + +### Leetcode +- [62. Unique Paths](https://leetcode.com/problems/unique-paths/description/) +- [64. Minimum Path Sum](https://leetcode.com/problems/minimum-path-sum/description/) +- [70. Climbing Stairs](https://leetcode.com/problems/climbing-stairs/description/) +- [198. House Robber](https://leetcode.com/problems/house-robber/description/) + + +> 延伸閱讀:[演算法筆記:Knapsack Problem](https://web.ntnu.edu.tw/~algo/KnapsackProblem.html) + +## KMP 演算法 +![image](https://hackmd.io/_uploads/ry2SjfO4C.png) + +KMP演算法是一種用於字串搜尋的高效方法。透過建立部分配對表,可以快速定位搜尋字串中的可能配對位置,減少不必要的重複比較,從而加速搜尋過程。 + +- [KMP algorithm,從自學到放棄 (1)](https://medium.com/@c.s.fangyolk/kmp-%E6%BC%94%E7%AE%97%E6%B3%95-%E5%BE%9E%E8%87%AA%E5%AD%B8%E5%88%B0%E6%94%BE%E6%A3%84-1-7f71e65839a0) +- [KMP algorithm,從自學到放棄 (2)](https://medium.com/@c.s.fangyolk/kmp-%E6%BC%94%E7%AE%97%E6%B3%95-%E5%BE%9E%E8%87%AA%E5%AD%B8%E5%88%B0%E6%94%BE%E6%A3%84-2-94dda22f80b2) +### Leetcode +- [28. Find the Index of the First Occurrence in a String](https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/description/) +## Backtracking +## Divide-and-Conquer +### Leetcode +- [108. Convert Sorted Array to Binary Search Tree](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/) +## Brute Force +## Greedy Method + +## Graph 進階議題 +### 最短路徑(Shortest Path) +- [Shortest Path:Intro(簡介)](https://alrightchiu.github.io/SecondRound/shortest-pathintrojian-jie.html) +### 戴克斯特拉演算法(Dijkstra's Algorithm) +- [基礎演算法系列 — Graph 資料結構與Dijkstra’s Algorithm](https://medium.com/%E6%8A%80%E8%A1%93%E7%AD%86%E8%A8%98/%E5%9F%BA%E7%A4%8E%E6%BC%94%E7%AE%97%E6%B3%95%E7%B3%BB%E5%88%97-graph-%E8%B3%87%E6%96%99%E7%B5%90%E6%A7%8B%E8%88%87dijkstras-algorithm-6134f62c1fc2) + +### Leetcode +- [743. Network Delay Time](https://leetcode.com/problems/network-delay-time/description/) +- [787. Cheapest Flights Within K Stops](https://leetcode.com/problems/cheapest-flights-within-k-stops/description/) +--- + +### 生成樹(Spanning Tree) +![image](https://hackmd.io/_uploads/S1zbFo8eC.png) + + +- [演算法筆記:Spanning Tree](https://web.ntnu.edu.tw/~algo/SpanningTree.html) + +--- + +### 有向無環圖(Directed Acyclic Graph)與拓樸排序(Topological Sorting) +![image](https://hackmd.io/_uploads/B1txFj8gR.png) + + +- [演算法筆記:DAG](https://web.ntnu.edu.tw/~algo/DirectedAcyclicGraph.html) + +### Leetcode +- [210. Course Schedule II](https://leetcode.com/problems/course-schedule-ii/description/) + +# 附錄:Complexity Cheat Sheet +![](https://hackmd.io/_uploads/BkfMZStGp.png) +![](https://hackmd.io/_uploads/BJ50lHYGp.png) \ No newline at end of file diff --git a/source/_drafts/summer-intern-2024.md b/source/_drafts/summer-intern-2024.md deleted file mode 100644 index 348407d..0000000 --- a/source/_drafts/summer-intern-2024.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: 2024 年科技業暑期實習面試心得 -categories: - - 工作紀錄 -tags: - - Internship - - Interview -cover: /img/cover/tsmc.jpg -date: 2024-01-29 20:57:07 ---- - -一年又過去啦~又到了要找暑期實習的時間了,如果還沒看過去年的暑期實習面試心得文可以 [點我](https://jackchen890311.github.io/2023/05/24/summer-intern-2023/) 觀看。今年我的策略一樣是找大公司,因為順利的話畢業也許可以直接拿 Return Offer,就不用再找工作了,另外可能主要也會找 SWE、AI & ML 相關的職缺為主。 - -# 面試紀錄 -以下粗略地按照投遞時間排序。 -- 投遞履歷:2 家公司 -- 面試邀請:1 家公司 -- Offer Get:0 家公司 - -## Google - SWE Intern -### 第一關(1/29 & 1/30)- Online Coding Interview x 2 -一月初投的,中間兩三周都沒收到任何消息,還以為涼去,結果到快月底了才收到面試邀請,還好我一月都算有在加減刷題,話雖如此但面試前幾天還是緊張到靠北。題外話,我原本選一中一英,但莫名其妙變成二中,不知道該開心還是該難過,因為我也不知道到底語言會不會影響評分 @@。 - -以下因為 Google 有要求不能洩漏題目,所以我不會講到題目細節,只會提到大概的方向。其實考題完全不難,是很基本的資料結構(Tree, Matrix, Array 等等),暖身題也都只是基本的遍歷,看來真的是很考驗你基本功夠不夠穩,以及你夠不夠有自信。 - -#### 1/29 - Removal Sequence of a Tree - - - -題目:給定一個 Tree (Binary or N-ary),找到一個符合不同特定條件的 removal sequence - -其實很仁慈~第一題我算蠻快就解出來了,就是基本的 Traversal 而已。但第二題不知為啥我卡了蠻久,就是沒想到用 node height 就可以輕鬆解決,而且我好像還把 height 跟 depth 的概念講混了,我先哭死。不過後來在面試官提點之後,有在時間內做出來,但 complexity analysis 我講得很不確定,基本功真的不夠紮實嗚嗚嗚,感覺這種題目高手來應該是秒殺。 - -#### 1/30 - Matrix Traversal - - - -題目:給定一個 matrix,判斷是否能從起點走到終點,以及符合某條件下的最佳路徑 - -也是很仁慈,但我覺得我也沒有發揮好,第一題 Traversal 的部分寫完了被問有沒有辦法加速,結果我想了半天還沒有加速到,還浪費一堆時間(其實我寫的應該對了,但面試官一這樣問我就開始自我懷疑 @@,所以壓縮到第二題的時間)。第二題也是一開始沒確認好細節,所以稍微拖到,最後第二題就沒寫完,但概念都有講清楚,就是 Traverse 兩次加一些小細節這樣。面完感覺高手也是可以輕鬆秒殺的,我還卡成這樣大概是沒希望 QQ。 - - - -## Yahoo - Research Scientist Intern -這個職位剛好實驗室的學長前年有待過,他強烈推薦我去,因為他說風氣很不錯,加上可以自由研究你想研究的東西,甚至也許會對碩論有幫助,總之就是個不錯的職位這樣。也是一月初投,但他們好像一月底才會統一開始審這樣。 - -# 結語 \ No newline at end of file diff --git a/source/_posts/python-tutorial-1.md b/source/_posts/python-tutorial-1.md index f0acdd0..5d7d089 100644 --- a/source/_posts/python-tutorial-1.md +++ b/source/_posts/python-tutorial-1.md @@ -96,11 +96,10 @@ The loop ends. #### 變數(Variable) -- 我們會需要變數來存放數值運算的結果 -- [基本命名規則](https://ithelp.ithome.com.tw/articles/10217188) +- 我們會需要變數來存放數值運算的結果,使用 `=` 可以將數值指派給變數,可以參考 [基本命名規則](https://ithelp.ithome.com.tw/articles/10217188) +- 若重複指派給相同名稱的變數,原本的值會被覆蓋掉! - `a = 10` 意為指派 10 給 a(右邊的值丟給左邊的容器) - `a == 10` 意為比較 a 是否等於 10(為邏輯判斷式) - ``` >>> width = 20 >>> height = 5 * 9 @@ -128,8 +127,8 @@ The loop ends. - 字串 string - `"This is a string"` - 布林值 Boolean - `True` (Non-zero) / `False` (Zero) - > 補充:String 是由 Character 組成的陣列,為了便於操作在 Python 內多使用 String - > Python 在讀入長度為一的字母時,為了方便也會以 String 的方式儲存 + > 補充:String 是由 Character 組成的陣列,其他語言有可能會將 String 與 Character 當作兩種資料類別,但在 Python 中沒有 Character 的概念,因此長度為一的字母在 Python 中也會被當成字串來做處理。 + #### 型別轉換(Casting) ``` >>> str(3) diff --git a/source/_posts/python-tutorial-3.md b/source/_posts/python-tutorial-3.md index a48668c..ad8f6a2 100644 --- a/source/_posts/python-tutorial-3.md +++ b/source/_posts/python-tutorial-3.md @@ -85,17 +85,17 @@ The loop ends. - 示範: ```python= -while 4 > 3: # A Condidtion always True +while 4 > 3: # A always true condidtion print('Loop') ``` #### Range() -- 幫助我們創造一個範圍的函數 -- 用法: - - `range(n)` 會回傳 `[0,1,2,...n-1]` 的清單 - - `range(m,n)` 會回傳 `[m,m+1,m+2,...n-1]` 的清單 - - `range(m,n,k)` 會回傳 `[m,m+k,m+2k,...]` 的清單,最後一個元素不超過 n-1 -- 一般狀況使用第一個就好 +上面的例子中有使用到 range(),而 range() 是能夠幫助我們創造一個範圍的函數,其用法為: +- `range(n)` 會回傳 `[0,1,2,...n-1]` 的清單 +- `range(m,n)` 會回傳 `[m,m+1,m+2,...n-1]` 的清單 +- `range(m,n,k)` 會回傳 `[m,m+k,m+2k,...]` 的清單,最後一個元素不超過 n-1 + +一般的情況下使用第一個就好。 > 備註:回傳型態其實不完全是清單,但我們先把它當成清單用就好 > 可以透過 `list()` 將其轉為清單 diff --git a/source/_posts/python-tutorial-4.md b/source/_posts/python-tutorial-4.md index 90c6a22..d9e6ca8 100644 --- a/source/_posts/python-tutorial-4.md +++ b/source/_posts/python-tutorial-4.md @@ -48,6 +48,9 @@ date: 2023-03-23 20:54:38 >>> print(l + l) [1, 1.0, 100, "test", 1, 1.0, 100, "test"] + +>>> print(l * 3) +[1, 1.0, 100, "test", 1, 1.0, 100, "test", 1, 1.0, 100, "test"] ``` - Traverse a list: @@ -103,9 +106,9 @@ Output: # Correct Copy aList = [1, 2, 3] # Three different ways to copy a list (Shallow) -anotherList = list(a) -anotherList = a[:] -anotherList = a.copy() +anotherList = list(aList) +anotherList = aList[:] +anotherList = aList.copy() anotherList[0] = 5 print(aList) diff --git a/source/_posts/python-tutorial-5.md b/source/_posts/python-tutorial-5.md index 8bcd7d8..feddac4 100644 --- a/source/_posts/python-tutorial-5.md +++ b/source/_posts/python-tutorial-5.md @@ -17,23 +17,44 @@ date: 2023-03-23 20:54:42 我們也可以透過特定語法來定義自己的函數,透過函數可以幫我們達成「模組化」,省去重複的 code 同時提供更多彈性來執行類似的動作。 -- 一個函數包含名稱、函數本體、輸入(Input)與輸出(Output) - - 後兩者又叫做參數(Parameters)、回傳值(Return Values) - - 參數與引數(Arguments)雖然都指函數輸入值,但有些微的差異,可以參考[這裡](https://notfalse.net/6/arg-vs-param) -- 函數的使用: - - 用名稱來使用(或呼叫)函數,用 `def` 來定義函數 - - 函數的參數可以自行命名(如下例的 n),也可以傳入多個參數或給定預設值 - - 把要執行的程式碼包在函數本體中 - - 使用 `return` 來控制函數的結束點,並將回傳值放在後面(可放多個用逗號隔開) - - 若沒有 `return` 則函數跑完所有 code 一樣會結束並回傳 `None` - - 函數結束後會回到主程式,繼續執行後面的程式 -- 例子: +一個函數包含名稱、本體、輸入(Input)與輸出(Output),後兩者又叫做參數(Parameters)與回傳值(Return Values)。有時我們也會在函數最一開始的地方加入註解,來說明函數的使用方式以及參數 / 回傳值類型。 +![image](https://hackmd.io/_uploads/ByAuqF8kR.png) + +以下圖為例,輸入是蘋果,輸出是切半的蘋果,函數 `h` 的作用是把蘋果切半。 +![image](https://hackmd.io/_uploads/HJDXscDyA.png) + + +#### 名稱 +- 用關鍵字 `def` 來宣告函數,名稱接在 `def` 後面 +- 名稱通常會取與函數作用相關,便於使用者理解函數功能 +- 使用函數時,用其名稱來呼叫函數 + +#### 本體 +- 把要執行的程式碼包在函數本體中 +- 有時會在本體前面加上註解,用以說明函數功能 + - 說明最好包含:輸入、輸出、作用 + - 因為函數沒有限制變數的類別,所以最好在說明中講清楚 + +#### 輸入(參數) +- **參數的作用是提供資料給函數操作** +- 函數的參數可以自行命名(如下例的 n) +- 可以傳入多個參數,用逗號隔開 +- 可以給定預設值(如下例的 n = 5) + +#### 輸出(回傳值) +- **回傳值的作用是把結果回傳給使用函數的人** +- 使用 `return` 來控制函數的結束點,並將回傳值放在後面 +- 若沒有 `return` 則會自動在最後加上 `return None` +- 可放回傳多個結果,用逗號隔開 +- 函數結束後會回到主程式,繼續執行後面的程式 + +以下是一個在 python 中的實際例子,輸入是一個數字 `n` ,輸出是一個清單 `alist` ,函數 `get_1_to_n` 的作用是獲取 1 ~ n 的清單。 ```python= def get_1_to_n(n = 5): - print('Getting a list range from 1 to',n, end=': ') - print(list(range(1,n+1))) - return list(range(1,n+1)) + print('Getting a list range from 1 to',n) + alist = list(range(1,n+1)) + return alist x = get_1_to_n(10) # or get_1_to_n(n = 10) print('X = ',x) @@ -43,41 +64,48 @@ print('Y = ',y) ``` ``` Output: -Getting a list range from 1 to 10: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +Getting a list range from 1 to 10 X = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ~~~~~~~~~~ -Getting a list range from 1 to 5: [1, 2, 3, 4, 5] +Getting a list range from 1 to 5 Y = [1, 2, 3, 4, 5] ``` +> 補充:[Python yield的用法詳解](https://medium.com/ai%E5%8F%8D%E6%96%97%E5%9F%8E/python-yield%E7%9A%84%E7%94%A8%E6%B3%95%E8%A9%B3%E8%A7%A3-%E8%BD%89%E9%8C%84-52f539b67bdf) +> 補充:[參數(Parameters)與引數(Arguments)的差異](https://notfalse.net/6/arg-vs-param) #### 變數範圍(Scope of Variable) -變數依據生命週期的不同,分為全域(Global)變數與區域(Local)變數 -- 區域變數 +變數依據生命週期的不同,分為全域變數與區域變數。 +- 區域變數(Local Variable) - 定義在函數內的變數稱為區域變數 - 只能在函數內使用,函數結束後變數也會跟著消失 -- 全域變數 +- 全域變數(Global Variable) - 定義在函數外的變數稱為全域變數 - 所有地方(包含函數內)都可以使用,直到程式結束執行才會消失 - 若函數內宣告與全域變數同名的變數,則會被當作是區域變數,對其進行的操作不影響全域變數 -- 如果想在函數內修改全域變數,可以使用 `global var` 來進行(但不推薦,容易混亂) - 通常若我們需要拿到函數內的某個變數,我們會直接使用 `return var` - ```python= -def multiply_5(n): - scale = 5 - return n * scale - -# print(scale) will cause error -scale = 3 -result = multiply_5(2) -print(result) +scale = 3 # Global + +def multiply(num): + return num * scale + +def multiply_5(num): + scale = 5 # Local + return num * scale + +print(scale) +print(multiply(2)) +print(multiply_5(2)) print(scale) ``` ``` Output: +3 +6 10 3 ``` +> 補充:在函數內修改全域變數與上一層變數的方法:[Global & Nonlocal](https://ktinglee.github.io/LearningPython100days(6)_global_and_nonlocal/) #### 可變物件(Mutable Object)與不可變物件(Immutable Object) 在 Python 中,不同資料類別又可以其性質分為可變物件與不可變物件。 @@ -92,35 +120,27 @@ Output: ```python= # Mutable Object alist = [1,2,3] -alist[1] = 100 -print(alist) -``` -``` -Output: -[1,100,3] -``` +alist = [4,5,6] # Okay +alist[1] = 100 # Okay -```python= # Immutable Object astring = 'string' -astring[3] = 'A' +astring = 'STRING' # okay +astring[1] = 'A' # TypeError: 'str' object does not support item assignment ``` -``` -Output: -TypeError: 'str' object does not support item assignment -``` - 接著我們來看看記憶體位址的變化: ```python= # Let's take a look on the addresses of these objects # id() is a function help finding address of a variable +# Mutable Object alist = [1,2,3] print(id(alist)) alist[1] = 100 print(id(alist)) print('=====') +# Immutable Object astring = 'string' print(id(astring)) astring = 'STRING' @@ -137,12 +157,17 @@ Output: > 參考 [什麼是 Immutable & Mutable objects](https://www.maxlist.xyz/2021/01/26/python-immutable-mutable-objects/) -> *關於 set 可不可變其實有點爭議,在這裡先當作他是可變的,參考[這裡](https://stackoverflow.com/questions/14193438/are-python-sets-mutable) - +> \*關於 set 可不可變其實有點[爭議](https://stackoverflow.com/questions/14193438/are-python-sets-mutable),在這裡先當作他是可變的 +> #### Pass by Assignment - Example Illustration 此處我們「不會」深入講當傳參數時發生了什麼事情,因為牽扯到一些記憶體跟參照等等的概念,我們會用幾個例子來說明何謂 Python 的 Pass by Assignment。 -Python 中函數依據傳入參數的類別不同,會有不同的行為。**當傳入參數可變物件時,若未經重新指派,而是在函數裡直接修改參數,則會原始變數的值也會一同被修改(若經重新指派則視為全新的變數,即不影響原始變數);而當傳入參數為不可變物件時,任何對參數的操作都不影響原始變數(除非使用全域變數方式修改)**。 +Python 中函數依據傳入參數的類別不同,會有不同的行為。 +- 當傳入參數可變物件時: + - **若未經重新指派,而是在函數裡直接修改參數,則會原始變數的值也會一同被修改** + - **若經重新指派,則視為全新的變數,原始變數不會被影響** +- 當傳入參數為不可變物件時: + - **任何對參數的操作都不影響原始變數(除非使用全域變數方式修改)** 聽起來很複雜對吧?我們直接用例子來看會比較清楚一些: @@ -178,20 +203,26 @@ string #### 遞迴(Recursion) - An example on factorial +遞迴是一種概念,指的是「在函數在執行過程中呼叫自己」的方法。這種技術對於解決某些複雜問題特別有用,例如處理樹狀結構、遞迴搜尋、組合數學等。以下是遞迴的基本概念和特性: +1. 基礎案例(Base Case):遞迴函數必須有一個基礎案例,也就是遞迴呼叫的終止條件。當滿足這個條件時,遞迴將不再進行,從而避免無限迴圈。 +2. 遞迴案例(Recursive Case):如果沒有滿足基礎案例的條件,函數就會進入遞迴案例。在這個案例中,函數會呼叫一個較小的子問題版本。 +3. 問題簡化:遞迴案例通常將原始問題簡化為一個較小的子問題,直到滿足基礎案例為止。 ```python= -def factorial(n): - if n == 0: +def find_fact(n): + if n == 1: # base case return 1 - else: - recurse = factorial(n-1) + else: # recursive case + recurse = find_fact(n-1) result = n * recurse return result ``` +![image](https://hackmd.io/_uploads/S1gBbY810.png) -關於函數還有很多可以講:Recursion 的設計方法、Call by Reference、Call by Value... -但有些東西太進階了,我們先停在這裡,以後有機會再細講。 > 補充:[遞迴深度的上限](https://clay-atlas.com/blog/2020/09/20/python-cn-recursionerror-maximum-recursion-depth-exceeded/) +關於函數還有很多可以講:Recursion 的設計方法、Call by Reference、Call by Value...。但有些東西太進階了,我們先停在這裡,以後有機會或是遇到的時候再細講。 + + ### 其他常見資料結構 #### 元組(Tuple) @@ -199,7 +230,6 @@ def factorial(n): - 不同於 list 使用 `[]` ,tuple 使用 `()` - 一個元素的 tuple 須以 `(item, )` 表示 - 因屬不可變物件,故僅能以重新指派的方式修改其值,如下例: - ``` >>> mytuple = (11, 22, 33) >>> saved = mytuple @@ -210,12 +240,11 @@ def factorial(n): (11, 22, 33) ``` - zip() - ```python= num = [1,2,3] char = ['a','b','c'] CHAR = ['A','B','C'] -for i in zip(num,char,CHAR): +for i in zip(num, char, CHAR): print(i) ``` ``` @@ -230,22 +259,23 @@ Output: - 字典的組成包含鍵(Keys,不可變)與值(Values,可變) - 使用 Key 來尋找對應的 Value,以上述例子來說即為使用字尋找讀音 - Key 跟 Value 可以是任何資料類別,也可以不用一樣 - - 字典是無序的(在 collection 這個 library 中有提供有序字典) + - 字典是無序的(在 `collections` 這個 library 中有提供有序字典) - 若查找不存在的 key 則會報錯,可以使用 `in` 或 `dict.get()` 來檢查 ```python= mydict = dict() # or mydict = {} print("Here is an empty dictionary:", mydict) +# Add new pair in dictionary mydict[1] = 'one' mydict[2] = 'two' mydict[3] = 'three' -print(mydict) -print("2 is corresponding to:",mydict[2]) +print("Dictionary now looks like: ", mydict) +print("2 is corresponding to:", mydict[2]) # Access value ``` ``` Output: Here is an empty dictionary: {} -{1: 'one', 2: 'two', 3: 'three'} +Dictionary now looks like: {1: 'one', 2: 'two', 3: 'three'} 2 is corresponding to: two ``` @@ -291,7 +321,9 @@ Difference(B-A): {44, 55} Symmetric difference: {11, 44, 22, 55} ``` -> 特別注意 [運算元優先順序](https://june.monster/python-101-operators-and-priority/) ! +![image](https://hackmd.io/_uploads/BkvMrFNM0.png) + +> 補充:特別注意 [運算元優先順序](https://june.monster/python-101-operators-and-priority/)! #### 統整 | 類別 | Tuple | List | Dict | Set | diff --git a/source/_posts/summer-intern-2024.md b/source/_posts/summer-intern-2024.md new file mode 100644 index 0000000..267a083 --- /dev/null +++ b/source/_posts/summer-intern-2024.md @@ -0,0 +1,125 @@ +--- +title: 2024 年科技業暑期實習面試心得(Google, Gamania, Appier) +categories: + - 工作紀錄 +tags: + - Internship + - Interview +cover: /img/cover/appier.jpg +date: 2024-06-28 15:57:07 +--- + +一年又過去啦~又到了要找暑期實習的時間了,如果還沒看過去年的暑期實習面試心得文可以 [點我](https://jackchen890311.github.io/2023/05/24/summer-intern-2023/) 觀看。今年我的策略一樣是只找大公司 & 有興趣的職位,因為順利的話畢業也許可以直接拿 Return Offer,就不用再找工作了。另外我主要是找暑期,以 SWE、AI & ML 相關的職缺為主。(雖然我最後去的是長期實習 XD) + +# 面試紀錄 +- 投遞履歷:18 家公司 +- 面試邀請:3 家公司 +- Offer Get:1 家公司 + +最後去 Appier 做 Machine Learning Scientist Intern,我也只有拿到這個 Offer。 + +## Google - SWE Intern +### 第一關(1/29 & 1/30)- Online Coding Interview x 2 +一月初投的,中間兩三周都沒收到任何消息,還以為涼去,結果到快月底了才收到面試邀請,還好我一月都算有在加減刷題,話雖如此但面試前幾天還是緊張到靠北。題外話,我原本選一中一英,但莫名其妙變成二中,不知道該開心還是該難過,因為我也不知道到底語言會不會影響評分 @@。 + +以下因為 Google 有要求不能洩漏題目,所以我不會講到題目細節,只會提到大概的方向。其實考題完全不難,是很基本的資料結構(Tree, Matrix, Array 等等),暖身題也都只是基本的遍歷,看來真的是很考驗你基本功夠不夠穩,以及你夠不夠有自信。 + +#### Removal Sequence of a Tree(1/29) + + + +題目:給定一個 Tree (Binary or N-ary),找到一個符合不同特定條件的 Removal Sequence + +其實很仁慈~第一題我算蠻快就解出來了,就是基本的 Traversal 而已。但第二題不知為啥我卡了蠻久,就是沒想到用 Node Height 就可以輕鬆解決,而且我好像還把 Height 跟 Depth 的概念講混了,我先哭死。不過後來在面試官提點之後,有在時間內做出來,但 Complexity Analysis 我講得很不確定,基本功真的不夠紮實嗚嗚嗚,感覺這種題目高手來應該是秒殺。 + +#### Matrix Traversal(1/30) + + + +題目:給定一個 Matrix,判斷是否能從起點走到終點,以及符合某條件下的最佳路徑 + +也是很仁慈,但我覺得我也沒有發揮好,第一題 Traversal 的部分寫完了被問有沒有辦法加速,結果我想了半天還沒有加速到,還浪費一堆時間(其實我寫的應該對了,但面試官一這樣問我就開始自我懷疑 @@,所以壓縮到第二題的時間)。第二題也是一開始沒確認好細節,所以稍微拖到,最後第二題就沒寫完,但概念都有講清楚,就是 Traverse 兩次加一些小細節這樣。面完感覺高手也是可以輕鬆秒殺的,我還卡成這樣大概是沒希望。 + +結果:過年後感謝信,哭了。後續有寄信去問面試表現,得到的回覆是:「其中一關很棒,但另一關在一開始對算法的使用不夠熟悉,如果不用提示會更好,所以沒有繼續往下進行。」人資人還是蠻不錯的,還有鼓勵我繼續加油,不過說實話我不知道是哪場不夠好(我猜是第一場),只好以後繼續加油增進基礎功。 + + + +後面兩個都是後來才紀錄的,所以可能不會到很詳細。 + +## Gamania - AI Researcher +其實原本沒有發現遊戲橘子有開缺,會投這個主要是實驗室的學長介紹,就給他履歷請他幫我內推了。遊戲橘子內部業務除了遊戲之外,也有跨足媒體、娛樂等等產業,因此他們的 AI Team 主要就是在分析這些資料,跟其遊戲本業倒不太相關。 + +### 第一關(4/26) - Team +第一關主要是個人經歷關以及公司介紹,沒有技術關。面試我的看起來是未來的同事,前面花十多分鐘講完我的經歷,他只追問了一點點,就開始介紹公司了。我當下就有種感覺是根本都沒問到重點或細節,就直接結束了,不太確定是對我沒興趣還是怎樣,但後來幾天又收到二面邀請,想說也許還是有機會。 + +### 第二關(5/3) - HR +他們的 HR 看起來蠻幹練的,看起來對自己的工作很熟悉,講解的也都很清楚。基本上這關就跟平常遇到的 HR 關差不多,問一些個人特質、經歷等等,所以也沒有太多好說的。最後 HR 還說如果有問題可以用 LinkedIn 聯絡他,當下覺得蠻有機會。 + +結果:很遺憾的,收到了感謝信。後來去問當初內推我的學長,他說雖然我的個人特質看起來不錯,但他們想找經歷更相關的,可能我在這塊的經驗相對較少吧,有點可惜。題外話,遊戲橘子的公司在內湖,離我家大概就是 5-10 分鐘車程,我每次家教完回家也都會經過,超近! + + +## Appier - ML Engineer Scientist +4/30 突然收到面試邀請,蠻驚喜的,因為這個時間已經有點晚了,不過他也不是暑期實習就是了。我其實也有投 Appier 的暑期實習,甚至在面這個職位中途收到感謝信,害我以為這個職位也告吹,結果仔細看發感謝信的是暑期職位,不是這個缺。雖然面試過程拖了快兩個月,但很幸運最後也拿到 Offer,Onboard Date 也差不多就是七月初,算是沒有偏離我原本的目標太遠(除了開學後要繼續工作以外,希望我不會忙到翻掉嗚嗚)。 + +### 第一關(5/7) - Tech +第一關應該是由同事來面,也是我認為最扎實的一關。整個面試過程大約快一小時,最一開始是兩題 Coding Test,主要是實作兩個常見的 Loss 與他們的 Gradient,會給你他們的公式,並且不限語言或使用套件,只有說算 Gradient 的時候不要 Call Loss.backward()。Coding 部分還算友善,接著是一題模擬題,要你設計一個 Ranking Algorithm,會給你相關的 User Logs,方法上沒有限制。這題我講了幾個不同的方式,他叫我挑一個講細節一點,所以我就把最近在看的 Contrastive Learning 做延伸,應用在他的題目裡。這部分問蠻細的,會問你 Loss 設計,還有延伸情境題,好在我認為我講的還可以,講完之後今天面試差不多就到這邊。 + +### 第二關(5/14) - Manager +後來收到二面邀請,這次應該是團隊主管。聊什麼內容我有點忘記了,但印象中大概是圍繞在個人相關經歷,針對他有興趣的去問細節,好像還有問最近研究方向之類的,總之都是在純聊天,好在後來也順利進到下一關。 + +### 第三關(5/20) - CTO +實習要面到 CTO 其實我蠻意外的,不過他排的時間只有不到半小時,所以我猜想應該就是簡單聊天?實際到當天面試,CTO 開場第一句話就問我說:「你是許老師的學生喔?」,讓我有點驚喜,後來才知道原來他也跟我同個 Lab,世界真小。後來跟他聊了幾句之後,便針對我目前的研究來問,問了一些技術細節,差不多就結束了。結束前想說機會難得,問他對於目前市場的看法,就再跟他多聊了十多分鐘。CTO 人蠻好的,很願意跟我分享他的看法,聊天過程也很愉快。 + +### 第四關(6/22) - CEO +原本想說面到 CTO,面的過程看起來也沒啥問題,應該是十拿九穩等著收 Offer 了。加上 HR 過幾天後又跟我約電話,我跟學長討論後,他說他覺得應該是要談 offer 細節,但殊不知他是要跟我約四面 CEO 的時間。四面啊!!!我這輩子還沒面過四面,更何況只是實習,不過看來 Appier 似乎近期作風都是這樣,後來也有聽說其他實習或正職也要面到 CEO。 + +另外,大家應該可以發現三面跟四面之間隔了一個月,那是因為 CEO 忙到只有週末的少數可以面試,加上他們人資或祕書又很拖,週五晚上才在問週六早上能不能面試,也沒有先確定我所有的 Availablity,總之一路來回拉扯了快一個月,好不容易才敲定一個時間面試,為此我還特地請了半小時家教的假,真的是十分折騰,還好人資都還有在幫忙我處理,也是很感謝他。 + +CEO 基本上也是純聊天而已,就跟他聊聊個人經歷、以往實習內容、最近研究、為什麼想來 Appier 這樣。過程中可以看出他思路還蠻快的,但還是會很有耐心聽你講完。聊完沒問題後,他口頭跟我說到這個階段會有 Offer 了,也讓我總算放下了心中的大石頭,然後就速速的結束的此次面試,整個時長不到十五分鐘,看來他應該是真的很忙。 + +結果:Offer Get(6/25)。Onboard 日期訂在兩週後(7/8),直到六月底才拿到 Offer,也算是一種極限操作了,還好投這麼多間,總算有一間順利的走完全部過程。 + + +## 其他投遞公司與職位 +因為丟的公司很多,加上也很多沒有後續,所以就不一間一間細講了,但還是把我有投的公司放在這裡給大家參考。 + +- Yahoo: Research Scientist Intern & Data Science Intern +- Microsoft: SWE Intern +- Wistron: SWE / AI Intern +- Realtek: SWE / FWE / AI Intern +- PicCollage: ML Intern +- Dell: SWE Intern +- MediaTek: SWE / FWE / AI Intern +- Trend: SWE & Test Intern +- Quid: ML Intern +- NVIDIA: Applied Simulation Research Intern +- Garmin: DS / DE / SWE Intern +- Qualcomm: ML / Web Intern +- Acer: AI Intern +- Cadence: SWE Intern +- Synopsys: SWE Intern + +整理完發現我也投太多,然後也一堆沒有收到後續,好難過 QQ。其實我原本今年不打算寫這篇文章的,因為如果全都沒上好像有點丟臉,還好最後有拿到一個 Offer,也因此才有這篇文章的產出。 + +再一個題外話,去年在 TSMC 實習的單位主管 T (詳情請看[這篇](https://jackchen890311.github.io/2023/09/11/intern-at-TSMC/)文章)有問我要不要回去他底下再當一次實習生,這次是在竹科,直接在他底下實習。雖然稍微有點心動,但我沒有很想在一間公司實習兩次,加上我對該部門的工作內容已經大概都知道了,實習的目的比較像是探索不同的職位 / 公司與自己的契合度,所以我後來就婉拒了他的邀約。 + + +# 結語 +每次要找工作都很痛苦呀...近幾年景氣不是太好,海投的結果常常是杳無音訊,希望最後拿到 Offer 的這個實習職位可以順利轉正,這樣我畢業就不用再重找工作了。也謝謝你閱讀到這邊,希望我少少的幾個面試經驗對你有幫助,也祝我們在未來找工作的路上都順順利利!若還有什麼問題,都歡迎再聯絡我詢問喔~ \ No newline at end of file diff --git a/source/img/cover/appier.jpg b/source/img/cover/appier.jpg new file mode 100644 index 0000000..b0bda36 Binary files /dev/null and b/source/img/cover/appier.jpg differ