---
# System prepended metadata

title: 2021 年「資訊科技產業專案設計」課程第 1 次作業

---

# 2021 年「資訊科技產業專案設計」課程第 1 次作業

contributed by < `老藍 - Old Blue` >
>[英文影片](https://youtu.be/dESR_jP4Vpk)
>[中文影片](https://youtu.be/TxlgiarQ04k)



> [作業要求](https://hackmd.io/@sysprog/info2021-homework1)

**R**epeat: 重複提問，確認自己理解原本的要求
**E**xamples: 針對題目條件，舉出符合的案例，不限於特例
**A**pproach: 說明自己打算採用的方案
**C**ode: 撰寫程式
**T**est: 若不能實際測試，說明驗證程式的方案
**O**ptimization: 優化你的演算法

* LeetCode 題號
    * Easy
        * [1. Two Sum](https://leetcode.com/problems/two-sum/) 
        * [27. Remove Element](https://leetcode.com/problems/remove-element/)
        *  [206. Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/)
        *  [21. Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/)
    * Medium
        * [116. Populating Next Right Pointers in Each Node](https://leetcode.com/problems/populating-next-right-pointers-in-each-node/)
        *  [138. Copy List with Random Pointer](https://leetcode.com/problems/copy-list-with-random-pointer/)


---
# 改進目標

## interviewer 
* 站在 interviewer 的角度思考，進而可以學習題目的多變性，不會只站在背誦者的角度。
    * 中文影片雙方幾乎都是背誦者，並沒有辦法了解人。
* 注重在 "inter" ，讓自己學習面試不是背誦，而是表現自己。

---
# 英文題目

## [206. Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/)

```cpp
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = new ListNode(0);
        ListNode* next = head -> next;
        while(head){
            head -> next = pre;
            pre = head;
            head = next;
            next = head -> next;
        }
        return pre;
    }
};
```

1. 這樣做會出現 UndefinedBehavior 因為在最後一次 next 先做了 head → next，此時 head 已經指向 NULL ，那 head → next 會是什麼？顯然很奇怪。
2. 所以我們可以改進一下，仔細看 `next = head -> next;` 我們其實多宣告了一次，第一次初始化，第二次是指標操作。但其實每次 while 操作都會需要  `next = head -> next;` ，直到 head 已經指向 NULL。
3. 然後可以發現 `next = head -> next;` 其實不一定要先指向  `head -> next;`  可以先確定 head 在哪裡再操作 next。

所以應該變成這樣

```cpp
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = NULL;
        while(head){
            ListNode* next = head -> next;
            head -> next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
};
```

Recursion

```cpp
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (!head || !(head -> next)) {
            return head;
        }
        ListNode* node = reverseList(head -> next);
        head -> next -> next = head;
        head -> next = NULL;
        return node;
    }
};
```
延伸閱讀：
* [你所不知道的C語言：遞迴呼叫篇 - HackMD](https://hackmd.io/@sysprog/c-recursion#%E9%81%9E%E8%BF%B4%E7%A8%8B%E5%BC%8F%E6%B2%92%E6%9C%89%E4%BD%A0%E6%83%B3%E5%83%8F%E4%B8%AD%E7%9A%84%E6%85%A2)

## [21. Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/)

1. 這樣做會出現 UndefinedBehavior 因為在最後一次 next 先做了 head → next，此時 head 已經指向 NULL ，那 head → next 會是什麼？顯然很奇怪。
2. 所以我們可以改進一下，仔細看 `next = head -> next;` 我們其實多宣告了一次，第一次初始化，第二次是指標操作。但其實每次 while 操作都會需要  `next = head -> next;` ，直到 head 已經指向 NULL。
3. 然後可以發現 `next = head -> next;` 其實不一定要先指向  `head -> next;`  可以先確定 head 在哪裡再操作 next。

所以應該變成這樣

```cpp
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = NULL;
        while(head){
            ListNode* next = head -> next;
            head -> next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
};
```
Recursion

```cpp=
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (!head || !(head -> next)) {
            return head;
        }
        ListNode* node = reverseList(head -> next);
        head -> next -> next = head;
        head -> next = NULL;
        return node;
    }
};
```
利用傳入的 parameter `l1`, `l2` 互相比較大小。

多宣告一個 `ListNode dummy;` ，**這個 dummy 之後會被拋棄，因為他是宣告出來指向第一個 node 的節點**，之後回傳的會是 `[dummy.next]` 。

再來多宣告一個 `ListNode *tail = &dummy;`  ，我們會利用它來修改 each node 應該指向的下一個 node。

相比結束後，第一步會是將 tail 指向 loser ，如果 l1 == l2 那指向誰都沒差，所以我們選擇保留原位，不修改指向的人。之後記得將 loser 只移動到下一個。

舉例：

l1 = 1 → 2 → 4 → NULL

l2 = 2 → 2 → 3 →8 → NULL

```cpp
1 round: l1=1 , l2=2
	l1 < l2
	dummy → 1(l1), l1 ++, l1 = 2

2 round: l1=2 , l2=2
	l1 == l2
	dummy → 1(l1) → 2(l2), l2 ++, l2 = 2

3 round: l1=2 , l2=2
	l1 == l2
	dummy → 1(l1) → 2(l2) → 2(l2) , l2 ++ ,l2 = 3

4 round: l1=2 , l2=3
	l1 < l2
	dummy → 1(l1) → 2(l2) → 2(l2) → 2(l1) , l1 ++ ,l1 = 4

5 round: l1=4 , l2=3
	l1 > l2
	dummy → 1(l1) → 2(l2) → 2(l2) → 2(l1) → 3(l2), l2 ++ ,l2 = 8

6 round: l1=4 , l2=8
	l1 < l2
	dummy → 1(l1) → 2(l2) → 2(l2) → 2(l1) → 3(l2) → 4(l1), l1 ++ ,l1 = NULL

7 round: break while ,因為 NULL && 8 = NULL

因為 8 還沒被 tail 指到，所以要判斷誰先到 NULL，要把剩下沒指到的 node，指回去。 
tail->next = (l1 != NULL) ? l1 : l2;
l1 == NULL 所以 tail->next = l2;

```

```cpp
class Solution {
public:
    ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) {
        ListNode dummy(0);
        ListNode *tail = &dummy;
        
        while (l1 && l2) {
            if (l1->val < l2->val) {
                tail->next = l1;
                l1 = l1->next;
            } else {
                tail->next = l2;
                l2 = l2->next;
            }
            tail = tail->next;
        }

        tail->next = (l1 !=NULL) ? l1 : l2;
        return dummy.next;
    }
};
```
可以寫得更簡潔
```cpp
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    ListNode l3;
    ListNode* cur = &l3;
    
    for  (; l1 && l2; cur = cur->next)
        (l1->val < l2->val) ? (cur->next = l1, l1 = l1->next) : (cur->next = l2, l2 = l2->next);
        
    cur->next = (l1) ? l1 : l2;
    return l3.next;
}
```

## [138. Copy List with Random Pointer](https://leetcode.com/problems/copy-list-with-random-pointer/)


這題因為是要 [deep copy](https://en.wikipedia.org/wiki/Object_copying#Deep_copy)，意思是需要複製一個一模一樣的資料，是有實際佔用記憶體的複製，稱為 copy by value。

而另一種我們只創建一個 pointer 並且只有指向預複製的對象，稱之為 [shallow copy](https://en.wikipedia.org/wiki/Object_copying) ，又稱為 copy by address。
```cpp=
int *first, *second;
first = new int[10];
second = first; //shallow copy
second = new int[10];
for (int idx=0;idx<10;idx++) //deep copy
    second[idx] = first[idx];
```

如果要 deep copy linklist，會面臨一些問題：
1. **next link & random link 的目標尚未創建**
    * 因為 next node or random node 尚未創建出來，所以只能先配置 NULL 。
    * 等全部創建完再 assign link 。
2. **The new node loses its address**
    * 創建的新 node 如果沒有任何 pointer 認養，大家都會變孤兒。宣告一個 pointer array 好像不是很好的解法，畢竟這會造成 **assign random link O(n^2)**。
        *  因為是 linklist 所以在配置的時候需要一個一個 node 去掃才能到達目標。所以如果每次 random link 最慘都在 linklist 的最尾巴那就需要 O(n)，然後有 n 個 node ，造就 Time complexity O(n^2)。


而 HashMap 剛好適合處理這個問題。
1. 建立一個 HaspMap ，裡面存放 < Old node , New node > 的記憶體位置。
2. 在 assign link 可以透過先訪問 Key (Old node) 再訪問 Vaule (New node)。

![](https://i.imgur.com/ZCTsLW6.jpg)
依照上圖 node1 的 random link (紅線)是 node3，可以先找 HashMap 中的  Old node3 再去訪問 New node3。


```cpp=
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/

class Solution {
public:
    Node* copyRandomList(Node* head) {
        Node *cur = head;
        unordered_map<Node*,Node*> Hashmap;
        while(cur){
            Node *copy = new Node(cur -> val); // 先建立 new node
            Hashmap[cur] = copy;// 放入 HashMap
            cur = cur->next;
        }
        
        cur = head;
        while(cur) {
        // assign link
            Hashmap[cur]->next = Hashmap[cur->next];
            Hashmap[cur]->random = Hashmap[cur->random];
            cur=cur->next;
        }

        return Hashmap[head];
    }
};
```

---

# 中文題目
## [1. Two Sum](https://leetcode.com/problems/two-sum/)

### C++

**R:** 
要在一組必有解的陣列中回傳兩個相異數字的 index，這兩個相異數字可以組成 target。

**E:** 
所以假設我給一個 {3,3,4} 的陣列，並且我要找 6，喔應該是要回傳 {0,1}，不可以是 {0,0}，{1,1}。



$O(n^2)$

**A:** 
很簡單利用兩個 for ，第一個 for 從 array 中指定 1 個 element ，再用第二個  for  scan array 其他的 element ，如果第二個 for scan 的其中一個 element 跟第一個 for 指定的 element 加出來為 target ，就可以回傳兩個 for 現在指向  array 的位置 。


$O(n)$

**O:** 這邊引用兩個想法

1. target = x + y
    - target - x = y
    - 減去 x 後，我們只需要再搜尋 y 是否存在。
2. Hash table
    - 為什麼要用 Hash table ，這是因為 Hash table "平均搜尋"時間通常只需要 O(1)，worst case 為O(n) (因為最慘可能要找 n 次。n 為陣列長度，因為可能 hash function 很慘，將所有數字都 hash 到同一個 bucket 裡)，O(1)∗n=O(n)。

```cpp
class Solution {
public:
    vector<int> twoSum(vector<int> &nums, int target) {
        unordered_map<int, int> m;
        for (int i = 0; i < nums.size(); i++) {
            if (m.count(target - nums[i])) {
                return {m[target - nums[i]], i};
            } else {
                m[nums[i]] = i;
            }
        }
        return {};
    }
};
```
我們利用 C++ 來解決這個問題。

C++ STL 中的 [unordered_map](https://en.cppreference.com/w/cpp/container/unordered_map) 就是用 hash table 所建立。
儲存的方式會是{key ,value} pair 
(Key = 陣列中的數值, Value = 陣列中的位置)
***{key = nums[i] , value = i}***

1. 先宣告一個 `unordered_map<int, int> m` 
2. 利用 for loop 來 sacn 整個 array(vector 是加強版的 array)
3. 再利用 if 判斷 target - x = y ，這個 y 是否在這個 Hash table 裡面
    * .count 這個 member function 可以搜尋指定的 key 值的個數
    * 所以按照剛剛的說法，m.count(裡面是要放 target - nums[i] (x) )
    * target - nums[i](x) = y
4. 如果有找到這個 key ，就回傳 vaule 跟當前 i 指向的位置
5. 如果找不掉就將這個數值跟它的位置放入 Hash table

**以 vector = {3,3,4}, target = 6 為例**
1. i = 0, target - num[0] = 3
2. Hash table 找不到 3 (因為剛建立)
    * m.count 回傳 0
3. 將 {Key = 3,value = 0} 丟入 hash table
4. i = 1, target - num[0] = 3
5. Hash table 找到 3 (因為剛剛丟入 {3,0})
6. return {0,1}



---


### C

08/20/2021 22:07	Accepted	12 ms	8.8 MB	c

沒找到 target 就插入 hash ，所以{ 2, 11, 15} 找 26 時會先插入 11 之後再直接用 15 。

$O(log_n)$
可以再更快，譬如鏈結的方式可以用 [Skip List](https://en.wikipedia.org/wiki/Skip_list)，或許時間複雜度可以降到 O(logn)，但是會增加空間複雜度。

```c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
typedef struct node{
	long value;
	int index;
	struct node *next;
}newnode;

//每次都插頭
void insert(newnode **hashtable,long value ,int index, int n){
	int t = abs(value) % n;
	newnode *temp = hashtable[t];
	newnode *add = (newnode*)malloc(sizeof(newnode));
	add->value = value;
	add->index = index;
	add->next = temp->next;
	temp->next = add;
}

int search(newnode **hashtable, long target,int n){
	int hashindex = abs(target) % n;
	newnode *temp = hashtable[hashindex]->next;
	while(temp){
		if(temp->value == target)
			return temp->index;
		temp = temp->next;
	}
	return -1;
}

void freeHashTable(newnode **hashtable, int n){
	int i = 0;
	newnode *temp = NULL;
	newnode *delete  = NULL;
	for(i = 0 ; i < n ; i++){
		temp = hashtable[i];
		delete = temp;
		while(temp){
			delete = temp;
			temp = temp->next;
			free(delete);
		}
	}
	free(hashtable);
}
int* twoSum(int nums[], int numsSize, int target) {
    int i = 0, j = 0;
    int n = numsSize, index = 0;
    int* result = NULL;
    newnode** hashTable = (newnode**)malloc(n * sizeof(newnode*));
    // init head node
    for(i = 0; i < n; i++)
        hashTable[i] = (newnode*)calloc(1, sizeof(newnode));
    for(i = 0; i < n; i++) {
        index = search(hashTable, target - nums[i], n);
        if(-1 == index)
            insert(hashTable, nums[i], i, n);
        else {
            result = (int*)malloc(sizeof(int) * 2);
            result[0] = index;
            result[1] = i;//因為減去 target 所以直接回傳 i
            freeHashTable(hashTable, n);
            return result;
        }    
    }
    freeHashTable(hashTable, n);
    return result;
}

int main(int argc ,char** argv){
    int *result = (int*)malloc(sizeof(int) * 2);;
    int numss[2] = {3,3};
    result = twoSum(numss,2,6);
    printf("%d,%d \n",result[0],result[1]);
    free(result);
    return 0;
}
```

---

For leetcode 

```c
**/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
typedef struct node{
	long value;
	int index;
	struct node *next;
}newnode;

void insert(newnode **hashtable,long value ,int index, int n){
	int t = abs(value) % n;
	newnode *temp = hashtable[t];
	newnode *add = (newnode*)malloc(sizeof(newnode));
	add->value = value;
	add->index = index;
	add->next = temp->next;
	temp->next = add;
}

int search(newnode **hashtable, long target,int n){
	int hashindex = abs(target) % n;
	newnode *temp = hashtable[hashindex]->next;
	while(temp){
		if(temp->value == target)
			return temp->index;
		temp = temp->next;
	}
	return -1;
}

void freeHashTable(newnode **hashtable, int n){
	int i = 0;
	newnode *temp = NULL;
	newnode *delete  = NULL;
	for(i = 0 ; i < n ; i++){
		temp = hashtable[i];
		delete = temp;
		while(temp){
			delete = temp;
			temp = temp->next;
			free(delete);
		}
	}
	free(hashtable);
}
int* twoSum(int* nums, int numsSize, int target , int* returnSize ) {
    int i = 0, j = 0;
    int n = numsSize, index = 0;
//
    *returnSize = 2;
    int *returnValues = malloc((*returnSize) * sizeof(int));
//
    newnode** hashTable = (newnode**)malloc(n * sizeof(newnode*));
    // init head node
    for(i = 0; i < n; i++)
        hashTable[i] = (newnode*)calloc(1, sizeof(newnode));
    for(i = 0; i < n; i++) {
        index = search(hashTable, target - nums[i], n);
        if(-1 == index)
            insert(hashTable, nums[i], i, n);
        else {
            returnValues[0] = index;
            returnValues[1] = i;
            freeHashTable(hashTable, n);
            return returnValues;
        }    
    }
    freeHashTable(hashTable, n);
    return returnValues;
}**
```
---
## [27. Remove Element](https://leetcode.com/problems/remove-element/)

### C++

**R:** 
要從一組陣列 `nums` 中移除 `val`，並且不可以出現空洞，所有被移除數值的位置需要有其他陣列的原本數字填上。

**E:** 
`nums` = {3,2,2,3}, `val` = 3，那麽應該輸出 {2,2,_,_}

**A:** 
宣告一個 `iterator it`，並且用一個 foor loop 來 scan 這個陣列(scan 陣列，會利用指標來 scan `num[i]`)。

`it`會跟著 num[i] 一起移動，如果 num[i] == val ，那麼因為 num [vector](https://en.cppreference.com/w/cpp/container/vector)，vector 有一個名為 erase 的 Member function，可以用來直接移除數字，並將 vector 後面的數字往前遞補，完全符合題目要求。

```cpp=
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        vector<int> :: iterator it;
        it = nums.begin();
        for(int i=0;i<nums.size();i++){
            if(nums[i] == val){
                nums.erase(it);
                it--;
                i--;
            }
            it++;
        }
        return nums.size();
    }
};
```
## [116. Populating Next Right Pointers in Each Node](https://leetcode.com/problems/populating-next-right-pointers-in-each-node/)
* [台詞](https://www.notion.so/116-6c21746d74fa4871bf5e8716bfedf43c)

**R**epeat:

給定一個 perfect binary tree，他所有的 leaf 都在同一層，並且只要是 parent node 一定會有兩個children node。

我們需要將這顆 tree 中所有的右節點往它右邊的節點(如果存在，不存在則為 NULL)透過 next 指標指向它。

**E**xamples:

1. 以下圖為例，3 在 2 右邊、5 在 4 右邊、6 在 5 右邊、7 在 6 右邊因此需要透過  struct Node 中的 next 指標來指向。

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

---

2. 如果只有一個 node ，它既是 root 也是 leaf，因此沒有 chidren ，不需要做任何動作。

    但是！他是特殊 case，會這樣說是因為 `Node* root` 可以只傳一個 node ，因此如果用 Recursion method 則需要視為跟“空集合”一樣的 case。 

    `if(!root || !root->right) return root;`

### C++

這題可用 Recursion 來做，那用 Recursion 的話就要考慮是用 Preorder(DLR)、Inorder(LDR)、Postorder(LRD) 來走訪。先考慮一件事：

如果我們先走訪 node 2(LRD or LDR)，Tree 走訪到 node 2 時，我們應該如何將 node 2 鏈結到 node 3 、node 5 鏈結到 node 6 ?

很顯然沒有辦法將其鏈結起來，這問題也稱為 Local view。因為要鏈結到 node 3 or node 6 都需要 node 2 parent node 1 的幫助(view)。

![](https://i.imgur.com/QxmJMh8.jpg)


那 DLR 可以行嗎？答案是肯定的！

如果我們在走訪 Node 1 時先幫 Node 2 中的 Next link 鏈結到 node 3，那就可以解決 Local View 的問題！因為我走訪到 Node 2 就可以直接透過 Next link 訪問 Node 3 進而訪問 Node 6。

![](https://i.imgur.com/2iHoWOh.jpg)


所以我們將想法撰寫成 code 其實只有三行。

1. 先將 Current Node 的 left Node 的 Next 指向 Current Node 的 right Node(如果有 Left Node 的話)。 
    
    `current->left->next = Current->right;`
    
2. 第一步破除 Local View 之後，我們再撰寫一個判斷式
    
    `if(current→Next)`
    
    為何需要它？因為如果沒有這條鏈結代表這個 current Node 應該是 Tree 中最右邊的 Node 之一，代表不需要再做鏈結。
    
    再來就是處理會跨越左右子樹的鏈結。
    
    `current->right->next = current->next->left;`
    

以上三行就貫徹這題的精神 Recursion method 的精神。

```cpp
class Solution {
public:
    Node* connect(Node* root) {
        if(!root || !root->right) return root;
        root->left->next = root->right;
        if(root->next)
            root->right->next = root->next->left;
        connect(root->left);
        connect(root->right);    
        return root;
    }
};
```
----
