---
# System prepended metadata

title: Garmin BPM Junior Developer Testing

---

# Garmin BPM Junior Developer Testing

## Basic Java Part

![](https://i.imgur.com/5heM7JN.png)

請根據上圖，回答以下三小題：
1. 紅色的 interface 與 綠色 class 差異為
    :::info
    從關係圖可以看到，紅色彼此之間會使用 `extends` 去繼承彼此的 interface，而綠色則是使用 `implements`。
    這個架構是代表物件導向中的 `工廠模式`，透過 `定義好規範 (interface)` 讓實際 `實現的類(Class)` 自行去寫出 `不同特性但相同功能` 的物件。
    :::
1. 請解釋實線與虛線之間的差異
    :::info
    實線：繼承 (extends)，透過繼承父類別來不重複撰寫相同的程式碼邏輯或規範。
    虛線：實現 (implements)，透過已經定義好的規範，撰寫其程式碼去實現該類的功能。
    :::
1. 請盡可能解釋釋**所有**的綠色區塊作用以及其間差異
    - List
        - ArrayList
            :::info
            ![image](https://hackmd.io/_uploads/S1ORLDnpp.png)

            - 這是一個動態陣列，隨著元素不斷塞入，它會申請新的記憶體空間並刪除舊的陣列來進行存放。
            - 它允許透過 `index` 獲取元素。
            - 記憶體位置是 `連續的`。
            - 建議使用場景：
                - 資料不會被頻繁地進行 `插入` 或 `刪除` 等操作。
                - 需要透過 `index`，而不是 `迭代` 快速獲取資料。
                - 資料大小不會發生 `劇烈變化`
            :::
        - LinkedList
            :::info
            ![image](https://hackmd.io/_uploads/ByRJDP26a.png)

            - 此資料結構中有許多 `獨立的節點 (物件)`，它使用 `指標` 和 `位址` 作為連結進行連街。
            - 記憶體位置 `不連續`。
            - 建議使用場景：
                - 資料將被 `頻繁操作`，例如插入或刪除。
                - `不需要`快速獲取數據。
                - 資料大小 `尚未確認`。
            :::
        - Vector
            :::info
            - 它與 ArrayList `具有相同的功能`，但它側重於 `執行緒安全 (Thread-Safe)`。
            - 由於執行緒安全的原因，其 `效能低於 ArrayList`。 
            - 在多執行緒場景下，可以利用 Vector 的 `synchronized 特性` 來 `防止資料競爭 (data race)`。
            - 以下程式碼片段用來實證 ArrayList 與 Vector 兩者特性：
            ```java
            import java.util.ArrayList;
            import java.util.Vector;
            import java.util.List;
            import java.util.concurrent.ExecutorService;
            import java.util.concurrent.Executors;
            import java.util.concurrent.TimeUnit;
            import java.lang.System;

            public class Test  {
                private static List<String> list = new ArrayList<String>();
                private static List<String> vList = new Vector<String>();

                public static void main(String[] args) throws Exception {
                    ExecutorService e = Executors.newFixedThreadPool(5);
                    e.execute(new WriterTask());
                    e.execute(new WriterTask());
                    e.execute(new WriterTask());
                    e.execute(new WriterTask());
                    e.execute(new WriterTask());

                    e.awaitTermination(5, TimeUnit.SECONDS);
                    System.out.println();

                    e.execute(new WriterTaskV());
                    e.execute(new WriterTaskV());
                    e.execute(new WriterTaskV());
                    e.execute(new WriterTaskV());
                    e.execute(new WriterTaskV());

                    System.out.println("Total elements in ArrayList is " + list.size() + ".");
                    System.out.println("Total elements in Vector is " + vList.size() + ".");
                }

                static class WriterTask implements Runnable {
                    @Override
                    public void run() {
                        long start = System.nanoTime();
                        for (int i = 0; i < 1000; i ++) {
                            list.add("a");
                        }
                        long end = System.nanoTime();
                        System.out.println("Worker finished the job of ArrayList in " + (end-start)/1000 + " micro seconds.");
                    }
                }

                static class WriterTaskV implements Runnable {
                    @Override
                    public void run() {
                        long start = System.nanoTime();
                        for (int i = 0; i < 1000; i ++) {
                            vList.add("a");
                        }
                        long end = System.nanoTime();
                        System.out.println("Worker finished the job of Vector in " + (end-start)/1000 + " micro seconds.");
                    }
                }
            }
            
            ```
            - 實驗結果：
            ![截圖 2024-03-11 下午7.40.57](https://hackmd.io/_uploads/rJlyRw2pp.png)

            :::
            - Stack
                :::info
                - Stack 是一個 LIFO 的資料結構，繼承自 `Vector`，也就是說他也有 `執行緒安全` 的特性。
                - 從 `Vector` 可以看到，底層邏輯是使用 `陣列` 實現的。
                    ```java
                    public class Vector<E> extends AbstractList<E>vimplements List<E>, RandomAccess, Cloneable, java.io.Serializable
                    {
                        protected Object[] elementData;
                        protected int elementCount;
                        protected int capacityIncrement;
                    }
                    ```
                - 常用於實現字串反轉、計算機實現和瀏覽器瀏覽紀錄中使用。
                - 若是 `不在乎執行緒安全`，則可以考慮 `ArrayDeque`，其 `性能更好`。
                :::
    - Queue
        - PriorityQueue
            :::info
            - PriorityQueue 是使用 `Heap` 資料結構的特性去實現的隊列，也就是說順序的優先級不是 FIFO，而是 `元素的升/降序` 或是 `使用 Comparator` 來制定排序規則。
            - 每次新增/刪除的時間複雜度為 `O(logn)`
            - 常用於使用 `任務調度`、`事件處理` 和演算法中需要 `對元素進行排序` 的場景。
            :::
        - Deque
            - ArrayDeque
                :::info
                - ArrayDeque 是一個 `雙向的隊列`，它可以實現 `FIFO` 或是 `LIFO` 的特性，其底層邏輯則是基於 `陣列` 來實現。
                - 相較於 `Linked List` 效能更好。
                - 不需要頻繁 `申請` 或 `釋放` 記憶體，可以更好利用 CPU 快取，提高訪問效率。
                - 在不考慮執行緒安全且效能優先的情況下，ArrayDeque 是 `隊列` 和 `堆疊` 的首選。
                :::
    - Set
        - HashSet
            :::info
            - HashSet 其實是使用 HashMap 所實現的，只不過在 value 的部分以 `固定的 Object 物件` 進行填充。
            - 其功能主要用於 `去除重複的元素`。
            - 其查找、插入與刪除的時間複雜度皆為 `O(1)`。
            :::
        - LinkedHashSet
            :::info
            - LinkedHashSet 是使用 LinkedHashMap 所實現的，其中這個資料結構會有 `雙向鏈結串列`，用來記錄彼此的 `順序`。
            - 它既可以 `快速查找`、`插入` 和 `刪除`，也可以 `記錄元素插入的順序`。
            - 其查找、插入與刪除的時間複雜度皆為 `O(1)`。
            :::
        - SortedSet
            - TreeSet
                :::info
                - TreeSet 是使用 TreeMap 所實現的，資料結構則是使用 `紅黑樹` 來維護元素的順序。
                - 它可以 `自動` 對集合中的元素進行 `排序`，主要是按照 `升降序` 或 `Comparator` 來制定排序規則。
                - 其查找、插入與刪除的時間複雜度皆為 `O(n)`。
                :::
 
## Coding Part

撰寫一個 Method 在傳入的字串陣列中，找最長的共有 prefix 字串，如果沒有共有的 prefix ，則回傳空字串即可。

> Example 1:

Input: strs = [“flower”,“flow”,“flight”]
Output: “fl”
Example 2:

> Example 2:

Input: strs = [“dog”,“racecar”,“car”]
Output: “”
Explanation: There is no common prefix among the input strings.

> Constraints:

0 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i] consists of only lower-case English letters.

### 解題思路

看到每個字串都需要進行比較，我有想到兩個解法：

:::info
解法一：根據字串長度進行排序，每次迭代都以最短的字串來逐一進行比較，直到找到不同字元或最短字串到頭。
![image](https://hackmd.io/_uploads/rJLVwF36T.png)
- 時間複雜度
    - O(nm)~O(n^2)，n 為字串陣列數，m 為最短字串長度
    - O(n^2) 排序最壞情況
- 空間複雜度
    - O(1)

解法二：可以使用一般 web 中常見的 router 配對方式 —— Trie Tree。
Trie Tree 會將每個字元當作是一個節點，並且往後插入新的節點直到該字串結束，如此一來，只要是共用的字元就會被當作是我們的 Prefix，就可以方便我們找到答案。
![image](https://hackmd.io/_uploads/BJNf5Ynaa.png)
- 時間複雜度
    - 插入：O(nm)，n 為字串陣列數，m 為平均字串長度
    - 查找：O(m)，m 為最長字串長度
- 空間複雜度
    - O(nm)，n 為字串陣列數，m 為平均字串長度

我個人認為解法二在 `重複使用` 的場景下時間複雜度較低，所以使用 Trie Tree 解題。
:::

### 程式碼邏輯
```java
import java.lang.StringBuilder;
import java.lang.System;

class Solution {
    
    // 字母大小
    public static final int ALPHABET_SIZE = 26;
    
    // Trie 節點
    public TrieNode root;
    
    // 字母對應 index
    public int indexs;
      
    // Trie node
    public class TrieNode {
        // 每個 TrieNode 都會對應 26 個字母
        public TrieNode[] children = new TrieNode[ALPHABET_SIZE];
      
        // 判斷是否為最終節點，如果 true 代表後續沒有節點
        public boolean isLeaf = false;
         
        // constructor
        public TrieNode() {
            for (int i = 0; i < ALPHABET_SIZE; i++)
                children[i] = null;
        }
    };
      

    // 如果該 node == null，直接插入該 key
    // 如果該 node 是最後的節點，isLeaf 標記成 true
    public void insert(String key) {
        TrieNode temp = root;
        int index = 0;
      
        for (int level = 0; level < key.length(); level++) {
            // 字元轉 index
            index = key.charAt(level) - 'a';

            // 如果 children == null，建立子 Trie Node
            if (temp.children[index] == null)
                temp.children[index] = new TrieNode();
      
            // 存入該字元的 index
            temp = temp.children[index];
        }
      
        // 標記最後為節點
        temp.isLeaf = true;
    }
      
    // 計算 Trie Node 中有幾個字母被記錄
    public int countChildren(TrieNode node) {
        int count = 0;
        for (int i=0; i<ALPHABET_SIZE; i++) {
            if (node.children[i] != null) {
                count++;
                indexs = i;
            }
        }
        return count;
    }
      
    // 解題函數
    public String longestCommonPrefix(String[] strs) {
        // 建立一個空 Trie Tree 準備儲存 String[]
        root = new TrieNode();
        
        // 迭代 String[]，將每個 String 存入 Trie Tree
        for (int i = 0; i < n; i++)
            insert(strs[i]);
      
        // 走訪節點來計算共同的前綴
        TrieNode temp = root;
        
        // 如果節點上只有一個字母，它可以記錄該字母是哪個 index，方便後續查找
        indexs = 0;
        
        // 字串拼接，效能高
        StringBuilder prefix = new StringBuilder();
      
        // 只要每次在該節點上，只有一個字母存在且 isLeaf == false，就代表這是共用前綴
        while (countChildren(temp) == 1 && temp.isLeaf == false) {
            temp = temp.children[indexs];
            prefix.append((char)('a' + indexs));
        }
        
        return prefix.toString();
    }
      
    // 執行
    public static void main(String args[]) {
        Solution s = new Solution();
        String arr1[] = {"flower","flow","flight"};
        String ans1 = s.longestCommonPrefix(arr1);
      
        if (ans1.length() != 0)
            System.out.println("The longest common prefix is "+ans1);
        else
            System.out.println("There is no common prefix");
            
        String arr2[] = {"dog","racecar","car"};
        String ans2 = s.longestCommonPrefix(arr2);
        
        if (ans2.length() != 0)
            System.out.println("The longest common prefix is "+ans2);
        else
            System.out.println("There is no common prefix");
    }
}
```

### 結果
```
The longest common prefix is fl
There is no common prefix
```

## Java Debug Part

請說明下列程式碼，可能會有的問題。

Problem A.
```java
List<IHat> hats = new ArrayList<>();
hats.add(new Ushanka()); // that one has ear flaps
hats.add(new Fedora());
hats.add(new Sombrero());
for (IHat hat : hats) {
    if (hat.hasEarFlaps()) {
        hats.remove(hat);
    }
}
```

:::info
解釋：
Java 在使用 `foreach` 時，編譯器會將其轉換成 `Iterator` ，轉換如下：
```java
Iterator it = hats.iterator();

while(it.hasNext()) {
    IHat str = (IHat)it.next();
    if (hat.hasEarFlaps()) {
        hats.remove(hat);
    }
}
```


如果在迭代的時候 remove 元素，就會發生 `ConcurrentModificationException`，如下：
```bash
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
	at org.example.Main.main(Main.java:23)
```

那麼我們可以點擊錯誤訊息，往下追蹤是哪裡出問題：

```java
// ArrayList.java:967
public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

// ArrayList.java:1013
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}
```

原來是 `checkForComodification()` 這個函數，在我們走訪下一個元素的時候，會先去確認 `modCount` 與 `expectedModCount` 是否相同。若不同就會出錯。

這個 `modCount` 就是 ArrayList 這個 Collection 修改的次數，也就是說如果有 `新增/刪除` 操作，`Collection` 就會進行增加。

當我們在 add() 三次時使用 foreach，這時 `expectedModCount` 就會是 `3`，可是當我們迴圈中使用 `remove()` 時候 `modCount` 就會加一變成 `4`，所以才導致噴錯的問題。


唯一的解法是，不要使用 Collection 來新增/刪除元素，而是 iterator()。

以下是 `iterator.remove()` 的原始碼：
```java
public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet); // 刪除上一個元素
        cursor = lastRet; // 更新下一个元素的 INDEX
        lastRet = -1; // 清空上一个返回元素的 INDEX
        expectedModCount = modCount; // 更新 ArrayList 的 expectedModCount
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}
```

可以發現 expectedModCount 會被更新，也就避免了 `ConcurrentModificationException` 的錯誤發生。
:::

:::info
Solution A.

```java
List<IHat> hats = new ArrayList<>();
hats.add(new Ushanka()); // that one has ear flaps
hats.add(new Fedora());
hats.add(new Sombrero());

// Method 1.
hats.removeIf(hat -> hat.hasEarFlaps());

// Method 2. 
Iterator<IHat> it = hats.iterator();
while (it.hasNext()) {
    IHat hat = it.next();
    if (hat.hasEarFlaps()) {
        it.remove();
    }
}
```

其實 `hats.removeIf(hat -> hat.hasEarFlaps());` 是 `Method 2.` 的語法糖，原始碼如下：
```java
default boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    boolean removed = false;
    final Iterator<E> each = iterator();
    while (each.hasNext()) {
        if (filter.test(each.next())) {
            each.remove();
            removed = true;
        }
    }
    return removed;
}
```
:::


Problem B.
```java
selfie = person.shootASelfie();
try {
    selfie.show();
} catch (NullPointerException e) {}
```

:::info
解釋：
一般來說，selfie 前面應該要有該 `資料型別`，如果沒有，可以初步判斷它是 `class 內已經宣告的變數` 或是 `區域變數`。
然後，selfie 這個物件會使用函數 `show()`，代表說這個 selfie 不能是 null，不然會產生 `NullPointerException`。
:::

:::info
Solution B.
```java
selfie = person.shootASelfie();
if (selfie != null)
    selfie.show();
else {
    // null 處理
}
```
:::

## ERD Part

![](https://i.imgur.com/iI5wLyL.png)

請說明上圖 Entity 之間的關係

:::info
這個 ERD 設計類似 [IMGUR](https://imgur.com) 這種圖片分享平台。
- 主要有四個一對多關聯
    - 一個相簿可以對應多個照片(或0個)
    - 一個地點可以對應多個照片(或0個)
    - 一個會員可以對應多個照片(或0個)
    - 一個照片可以對應多個留言(或0個)
- 一個多對多關聯
    - 照片可以有多個標籤(或0個)，而標籤而不局限於單一照片(或0個)

- 首頁
    - 熱門照片 (瀏覽量、留言數)、最新照片(uploadDate)
    - 搜尋
        - 根據 photo title
        - 根據 tag
        - 根據 album title
        - 根據 location name

- 會員管理 (管理會員資料)
    - 新增
    - 修改
    - 獲取
    - 刪除
    - email 驗證
    - 手機驗證
    - 地址驗證
- Album (Album 內可以沒有照片)
    - 根據 member_id
        - 新增
        - 修改
        - 獲取
        - 刪除 (裡面有沒有照片，跳出確定刪除通知)
        - 觀看數更新 (MQ、Redis)
- Photo
    - 添加 link 欄位，紀錄 cdn 位置
    - 根據 album_id, member_id, location_id
        - 新增
        - 修改
        - 獲取
        - 刪除
        - 照片隱私操作
        - 觀看數更新 (MQ、Redis)
- Location
    - 新增
    - 修改
    - 獲取
    - 刪除
- Comment
    - 根據 photo_id
        - 新增
        - 修改
        - 獲取
        - 刪除
- TagInPhoto
    - 根據 photo_id
        - 新增
        - 修改
        - 獲取
        - 刪除
- Tag
    - 新增
    - 修改
    - 獲取
    - 刪除
:::

## SQL Part

承上題，請寫下以下需求的 SQL。

1. 請列出所有 Photo 的 title 以及 description.
    :::info
    ```sql
    SELECT Title, Description
    FROM Photo;
    ```
    :::
1. 請列出在 Album 名稱為 “2021 員工旅遊” 的所有照片.
    :::info
    ```sql
    SELECT b.Title as Title, b.Description as Description
    FROM Album a
    LEFT JOIN Photo b ON a.photo_id = b.photo_id
    WHERE a.Title = '2021 員工旅遊';
    ```
    
    因為 LEFT JOIN 可能會有`有相簿還沒上傳照片`的情況發生，如果要避免 Photo 是 null 的話，可以使用 INNER JOIN。
    
    ```sql
    SELECT b.Title as Title, b.Description as Description
    FROM Album a
    INNER JOIN Photo b ON a.photo_id = b.photo_id
    WHERE a.Title = '2021 員工旅遊';
    ```
    :::
1. 請列出所有 member “John” 在 2021-1 月上傳且有留下 commet 的所有照片
    :::info
    這邊使用 INNER JOIN 代表，連接的右表是 NULL 的話就會被過濾，剛好可以找出有 COMMENT 的照片。
    ```sql
    SELECT b.Title as Title, b.Description as Description
    FROM Member a
    INNER JOIN Photo b ON a.member_id = b.member_id
    INNER JOIN Comment c ON b.photo_id = c.photo_id
    WHERE b.UploadDate = '2021-1' and a.Name = 'John';
    ```
    :::
1. (時間允許再作答) 請列出 address 在汐止的 member，上傳的照片 location 在日本，並有打上 Tag 為 “家庭旅遊” 的總觀看數 (view)，並根據會員名稱分別列出。
    :::info
    ```sql
    SELECT a.Name AS Name, SUM(b.View) AS View
    FROM Member a
    INNER JOIN Photo b ON a.member_id = b.member_id
    INNER JOIN Location c ON b.location_id = c.location_id
    INNER JOIN Photo_Tag d ON b.photo_id = d.photo_id
    INNER JOIN Tag e ON d.tag_id = e.tag_id
    WHERE a.Address = '汐止'
    AND c.Name = '日本'
    AND e.Title = '家庭旅遊'
    GROUP BY a.Name;
    ```
    :::

## Keyword

下列有許多關鍵字，請勾選可以解釋的項目，稍候一起討論。
- [X] Spring Framework
- [ ] ExtJS
- [ ] Struts
- [ ] Sitemesh
- [X] React
- [X] Spring Boot
- [ ] SSRS
- [X] MongoDB
- [X] Elatic Search
- [ ] Quartz
- [ ] Grails
- [X] Vim
- [X] Intellj
- [X] Guava
- [ ] Apaache Common Libary
- [X] Refactor
- [X] Docker
- [X] K8S
- [X] Git
- [X] SVN