[](https://hackmd.io/p1bIKDGDSfKEOnCntsm6MA)
# Homework 1
Shao-Ting Chiu (r07945001@ntu.edu.tw)
## Problem 1 - Complexity (80pts + 20pts)
##### 1. (5pt)
- loop 執行次數大約為 $n$ 次, 為線性關係, 故複雜度為 $O(n)$
##### 2. (5pt)
- 執行次數大約為 $log_{2}(n) - 1$ (例如 $8=2^3=2\times \underbrace{2\times 2}_{2次}$). 因此複雜度為 $O(log(n))$
##### 3. (10pt)
Time complexity of recurive functions [^master]
$$
\begin{align}
T(n) &= 4T(n-1) \\
T(n) &= 4 (4 T(n-2)) \\
T(n) &= 4^{n-c} 3T(c-1) \\
T(n) &= 4^{n-c} 3^c \\
T(n) &= O(4^n)_{\#}
\end{align}
$$
##### 4. (10pt)
Becsue $f(n)$ and $g(n)$ are positively define for $n$ as a positive integer:
$$ 0 \leq max(f(n) + g(n)) \leq f(n) + g(n) \leq 2\cdot max(f(n) + g(n))$$
for any $n\geq 1$.
We can find a $c_1\geq2$, such that fullfill the inequality:
$$f(n) + g(n) \leq c_1 max(f(n)+g(n))$$
And also a $c_2 \leq 1$, satisfies
$$c_2 max(f(n)+ g(n)) \leq f(n) + g(n)$$
for all $n\geq 1$. Therefore, the euqlity holds by applying the definition of $\Theta(n)$ [^ITA_theta]
##### 5. (10pt)
Because $f(n) = O(i(n))$ and $g(n) = O(j(n))$, there exist two integers $n_1$, $n_2$ and two constants $c_1$, $c_2$ that satisfy the inequalities:
<center>
$f(n) \leq c_1 i(n_1)$ and $g(n) \leq c_2 j(n_2)$
</center>
Let $n_0 = max(n_1, n_2)$ and $c_0 = c_1c_2$. Consider the equation $f(n)\cdot g(n)$:
$$f(n) \cdot g(n) \leq c_1 i(n) \cdot c_2 j(n) \leq c_0 i(n)j(n)$$
for all $n\geq n_0$. Thus $f(n)\cdot g(n) \leq O(i(n)\cdot j(n))_{\#}$ [^prop_o].
##### 6. (10pt)
The statement is **false**. Here is a contradictory example:
Let $f(n) = 2 \log_{2} n$ ; $g(n) = \log_{2} n$. This example satisfy the condition, $f(n) = O(g(n))$. However, when apply this example in the power of 2. We can find that $2^{f(n)} = 2^{ 2 \log_{2} n} = n^2$, and $2^{g(n)} = n$[^log]. Therefore, in this case, $2^{f(n)} \notin O(2^{g(n)})_{\#}$.
##### 7. (10pt) The harmonic series
Strategy: **Approximation by Integrals** [^ITA_1154]
Let $f(k) = \frac{1}{k}$, which is a **monotonically decreasing function**. We can find the bounds
$$\int_{m}^{n+1} f(x)dx \leq \sum_{k=m}^{n} \frac{1}{k} \leq \int_{m-1}^{n} f(x) dx$$
For a **lower bound**,
$$\sum_{k=1}^{n} \frac{1}{k} \geq \sum^{n+1}_{1} \frac{1}{x} dx = \ln(n+1)$$
For an **upper bound**,
$$ \begin{align*}
\sum_{k=2}^{n} \frac{1}{k} + 1 & \leq \int_{1}^{n} \frac{1}{x} dx + 1\\
& = \ln(n) + 1
\end{align*}$$
In summary,
$$\ln(n+1) \leq \sum_{k=1}^{n} \frac{1}{k} \leq \ln(n)+1$$
We need to find constants $c_1$ and $c_2$ that can include bounds by multply with $\ln(n)$.
- Lower bound: $\ln(n+1) > 1 \cdot ln(n)$
- where $c_1 = 1$ for $n \geq 1$
- Upper bound: $\ln(n) + 1 = \ln(e\cdot n) < 2\cdot \ln(n)$
- where $c_2 = 2$ for $n > e$
Therefore,
$$0\leq \ln(n) \leq \sum_{k=1}^{n} \frac{1}{k} \leq 2\ln(n)$$
for all $n>e$. By $\Theta$ notatoin: $\sum^{n}_{k=1} \frac{1}{k} = \Theta(\ln n) = \Theta(\frac{\log n}{\log e}) = \Theta(\log n)_{\#}$
##### 8. (10pts) $\log(n!) = \Theta(n\log n)$
The equation can be expanded
$$\log(n!) = \log(n) + \log(n-1) + \cdots + \log(1)$$
We can find the upper bound by setting all the terms as $\log(n)$, and get
$$\log(n!) \leq n\log(n)$$
On the other hand, $\log(n!)$ can be expanded
$$\log(n!) = \log(n * (n-1) * (n-1) * \cdots * 1) $$
By removing half of elements,
$$\begin{align*}
\log(n!) &\geq \log(n*(n-1)*\cdots*(\frac{n}{2})) \\
&\geq \log((\frac{n}{2})^{\frac{n}{2}}) = \frac{n}{2} \log(\frac{n}{2})
\end{align*}$$
The relation between $n\log(n)$ and $\frac{n}{2} \log(\frac{n}{2})$ can be further explained
$$\frac{n}{2}\log(\frac{n}{2}) = \frac{n}{2}(\log(n) - 1)$$
We need to find a $c$ that fullfills the following inequality:
$$c\cdot n\log(n) \leq \frac{n}{2}\log(\frac{n}{2}) $$
For $n\geq 4$ [^log],
$$\begin{align*}
\log n &\geq 2 \\
\frac{1}{4} \log n &\geq \frac{1}{2} \\
\frac{1}{4} \log n - \frac{1}{2} &\geq 0 \\
\frac{1}{4} n \log n - \frac{1}{2} n &\geq 0
\end{align*}$$
Add $\frac{1}{4}n\log n$ on both sides:
$$\frac{1}{2} n\log n - \frac{1}{2}n \geq \frac{1}{4} n\log n$$
Recall the lower bound
$$ \begin{align*}
\log(n!) &\geq \frac{n}{2} \log(\frac{n}{2}) \\
&\geq \frac{1}{4} n\log n
\end{align*}$$
for all $n\geq 4$.
In conclusion,
$$0\leq \frac{1}{4}n\log(n) \leq \log(n!) \leq n\log(n)$$
for all $n\geq 4$. The $\Theta(n!) = n\log(n)_{\#}$
##### 9. (10pts)
令 $g(n) = n\log n$.
每一個 $f(n)$ 都會產生 $2$ 個 $f(n-1)$. 直到 $\lfloor \frac{n}{2} \rfloor = 1$ 總計會產生 $n^{\log_2 2}$ 個 $\Theta(1)$ operations 在 leaves. 在分支的過程中, 會產生 $\sum_{j=0}^{\log_2 n - 1} 2^j g(\frac{n}{2^j})$ 個 operation 在 roots[^master_tree]. 所以可以得到以下關係
$$f(n) = \underbrace{\Theta(n^{\log_2 2})}_{Leaves} + \underbrace{\sum_{j=0}^{\lfloor \log_2 n - 1 \rfloor} 2^j g(\frac{n}{2^j})}_{Roots}$$
已知 $g(n) = \Theta(n\log n)$, 接下來比較 $2 g(\frac{n}{2})$ 和 $g(n)$ 的 complexity:
$$\begin{align*}
2 g(\frac{n}{2}) &= 2 \cdot \frac{n}{2} \cdot \log(\frac{n}{2}) \\
&= n \cdot \log(\frac{n}{2}) \\
&= n \cdot (\log(n) - 1) \\
&< \underbrace{n\cdot \log(n)}_{=g(n)}
\end{align*}$$
for all $n>2$, 可以找到 $c<1$ 滿足 $2 g(\frac{n}{2}) < c\cdot g(n)$
另外下界可以用,
$$\begin{align*}
2g(\frac{n}{2}) = log(\frac{n^n}{2}) & \geq log(n^{n/2}) \\
& = \frac{1}{2} n\cdot \log(n)
\end{align*}$$
for $n\geq 4$. Furthermore, $\lim_{n\rightarrow \infty} \frac{log(n^n/2)}{log(n^{n/2})} = 2$ [^xpx]. Therefore, $2g(\frac{n}{2}) = \Theta(g(n))$
因此可以得到 roots 的 complexity,
$$\begin{align*}
\underbrace{\sum_{j=0}^{\lfloor \log_2 n - 1 \rfloor} 2^j g(\frac{n}{2^j})}_{Roots} &\leq \sum_{j=0}^{\lfloor \log_2 n - 1 \rfloor} c^j g(n) + O(1) \\
&\leq g(n) \sum^{\lfloor \log_2 n - 1 \rfloor}_{j=0} c^j + O(1)\\
&= g(n) (\frac{1\cdot(1-c^{\lfloor \log_2 n - 1 \rfloor})}{1-c}) + O(1) \\
&= O(g(n))
\end{align*}$$
for $n>2$ and $c<1$. On the other hand, we can also find a lower bound by letting $c<\frac{1}{2}$ and $n>4$. Therefore
$$\underbrace{\sum_{j=0}^{\lfloor \log_2 n - 1 \rfloor} 2^j g(\frac{n}{2^j})}_{Roots} = \Theta(g(n))$$
Finally we get,
$$\begin{align*}
f(n) &= \Theta(n) + \Theta(n\log n) \\
&= \Theta(n(\log(n))_{\#}
\end{align*}$$
##### 10. (Bonus 10pts)
因為 $f_k(n) = O(n^2)$, 所以可以找到 $c>0$ 滿足 $f_k(n) \leq c \cdot n^2$.
$$\begin{align*}
\sum_{k=1}^{n}f_k(n) = \underbrace{f_1(n) +\cdots + f_n(n)}_{n} &\leq c_1n^2 + c_2n^2 + \cdots + c_nn^2 \\
&= (c_1+\cdots+c_n)n^2\\
&\leq n\cdot \max(c_1,\cdots,c_n) \cdot n^2 \\
&= n^3 \cdot \max(c_1, \cdots, c_n) \\
&= O(n^3)
\end{align*}$$
令 $c_{i}$ 為常數, 滿足 $f_{i}(n) \leq c_{i}n^2$. $i\in\{1,\cdots,n\}$
##### 11. (Bonus 10pts)
證明 $k=$ Euclidean algorithm $gcd(m,n)$ 的遞迴次數, 而且 $m>n$. 則 $y\geq F_k$, $F_k$ 是 $k^{th}$ Fibonacci number [^gcd3].
**Section 1: GCD 和 Fibonacci**
分析三個相連的步驟 $(m_{k+1}, n_{k+1})$, $(m_{k}, n_{k})$, $(m_{k-1}, n_{k-1})$. 則 $m_k = n_{k+1}$, $m_{k-1} = n_k$. 此外, $n_{k-1} = m_k~mod~n_k$, 所以 $m_k = q\cdot n_k + n_{k-1} $ 且 $q\geq 1$. 所以 $n_{k+1} \geq n_k + n_{k-1}$ 形成 Finbonacci 的不等式[^gcd].
而 $F_k \approx \frac{(\frac{1+\sqrt{5}}{2})^k}{\sqrt{5}}$[^gcd] (Section 2). 所以 $k = c\cdot \log(F_k) \leq O(log(m+n))_{\#}$
**Section 2: Fibonacci 的通式**
Fibonacci 數列是一個線性空間 (兩個 Fibonacci 數列 相加或相乘都是 Fibonacci[^fib])。 在這個定理基礎下,尋找等比的 Fibonacci series:
$$\begin{align}
a_{n-2} \times r &= a_{n-1} \\
a_{n-1} \times r &= a_n \\
a_{n-2} + a_{n-1} &= a_n
\end{align}$$
結合上述三項性質,將前兩項帶入最後一項,得到
$$r^2 -r -1 =0$$
得到 $r$ 的兩個根為實根 $\alpha$ 和 $\beta$ , 代表可以找到 $a_{k} = p \alpha^{k-1} + q \beta^{k-1}$, 其中 $p$ 和 $q$ 可以用起始條件確定如($a_{1}=1$, $a_{2}=1$)
---
## Problem 2 - Stack / Queue (60pts)
##### 1. (10pts)
```julia {.line-numbers}
struct Queue
head
tail
array # Index starts at 0
length # Length of array
end
function init_queue(length)
Q = Queue()
Q.head = 0
Q.tail = 0
Q.length = length
Q.array = zeros(length) # malloc an array
return Q
end
function enqueue(Q,x)
# Check memory
next_tail = (Q.tail+1) % Q.length
if Q.head == next_tail
raise OverflowError
end
Q.array[Q.tail] = x
Q.tail = next_tail
end
function dequeue(Q)
if Q.head == Q.tail
raise UnderflowError
end
x = Q.array[Q.head]
Q.head = (Q.head + 1) % Q.length
return x
end
function front(Q)
return Q.array[Q.head]
end
function size(Q)
if Q.tail < Q.head
s = Q.length - (Q.head - Q.tail)
else
s = Q.tail - Q.head
end
return s
end
function reverse(Q)
q_tmp = init_queue(2) # Q(1)
for i in 0:(size(Q)) # Q(n)
x = dequeue(Q) # Q(1)
enqueue(q_tmp, x) # Q(1)
x = dequeue(q_tmp) # Q(1)
enquque(Q) # Q(1)
end
end
```
See design diagram [^queueD]
- Remarks: `reverse`
- Emptness of helper queue:
- `q_tmp` is initiated as an empty queue. Besides, `enqueue` and `dequque` are paired, which garantee `q_tmp` is empty after the `for loop`
- $O(1)$ - extra space
- `q_tmp` is initiated with fixed size `2`.
##### 2. (10pts)
- `init_queue`: $O(1)$
- `enqueue(Q,x)`: $O(1)$
- `dequeue(Q)`: $O(1)$
- `size`: $O(1)$
- `reverse` 內的 `for loop` 為一層, 執行次數與資料數 `n` 成正比, 所以是 $O(n) \in O(n^2)$. (見 pseudo code `reverse` 的複雜度註解)
##### 3.
```julia {.line-numbers}
struct stack
array
length
end
struct deque
front :: stack
back :: stack
end
# Stack
function init_stack() # O(1)
s = stack()
s.length = 0
s.array = []
return s
end
function push(s::stack, x) # O(1)
s.array[s.length] = x
s.length = s.length + 1
end
function pop(s::stack) # O(1)
x = s.array[s.length - 1]
s.length = s.length - 1
return x
end
# Queue
function init_deque(q::deque) # O(1)
q = deque()
q.front = init_stack()
q.back = init_stack()
end
function push_front(q::deque, x) # O(1)
push(q.front, x)
end
function push_back(q::deque, x) # O(1)
push(q.back, x)
end
function swap_to_empty(q::deque) # O(n)
if (q.front.length == 0)
while(q.back.length !=0)
x = pop(q.back)
push(q.front, x)
end
else if (q.back.length==0)
while(q.front.length !=0)
x = pop(q.front)
push(q.back, x)
end
else
raise error
end
function pop_front(q::deque) # max(O(1), O(n)*2+O(1) )
if q.front.length > 0
pop(q.front)
else
swap_to_empty(q)
pop(q.front)
swap_to_empty(q)
end
end
function pop_back(q::deque) # max(O(1), O(n)*2+O(1) )
if q.back.length > 0
pop(q.back)
else
swap_to_empty(q)
pop(q.back)
swap_to_empty(q)
end
end
```
- Deque formed by two stacks
<img src="img/dequeue.png">
- push_front
<img src="img/push_front.png">
- pop_left
<img src="img/pop_left.png">
##### p.4 (5pts)
`push_front` 使用的是 stack 的 `push` . 因此, time complexity 為 $O(1)$
##### p.5 (5pts)
同 `push_front` 使用 stack 的 `push`. Time complexity 為 $O(1)$
##### p.6 (5pts)
如果 `front` stack 還有 element, time complexity 為 stack `pop` 的 $O(1)$. 然和 worst-case 是 `front` 沒有剩下 element, 需要把 `back` 底層的 element pop 出來. 這個 pseudo code 使用的方法是, 把 `back` 一直 `pop`, 且 `push` 到 `front`. 接著 `front` pop 一次, 取得 return 後, 再搬移回去. 這個動作使用了 `while` 需要的 time complexity 為 O(n) 計算如下
$$O(\text{pop\_front}(q)) = \begin{cases}
O(1), \text{q.front.length>0}\\
\underbrace{O(n)}_{\text{swap to front}} + \underbrace{O(1)}_{\text{pop}} + \underbrace{O(n)}_{\text{swap back}}, \text{q.front.length=0}\\
\end{cases}$$
而 $O$ 是 worst-case 複雜度, 因此 `pop_front` 的時間雜度為 $O(n)$.
##### p.7 (5pts)
同 `HW2 p.6` 的解釋, time complexity 為 $O(n)$.
##### p.8 (10 pts)
當加入 $n$ 個 `push`, 每當遇到 $3$ 的指數就要執行一次`enlarge` operation ($O(m)$). 因此可以得到在以下次數需要做 `enlarge` [^dymarray]:
$$1,3,9,27,\cdots, 3^{\lfloor \log_{3}n \rfloor}$$
例如當 $n=10$, $3^{\lfloor \log_{3}n \rfloor} = 3^{2}=9$, 就要在 $\{1,3,9\}$ 的時候 `enlarge`. 因此可以得到
|執行次數|執行時間|
|---|---|
|$$1,3,9,27,\cdots, 3^{\lfloor \log_{3}n \rfloor}$$|$1c, 3c, 9c, 27c,\cdots, 3^{\lfloor \log_{3}n \rfloor} c$|
$c$ 是常數, 使得 `enlarge` 為 $m\cdot c \in O(m)$.
因此當 `push` $n$ 次, `enlarge` 所需的總時間 $T$ 為:
$$\begin{align*}
T &= c\cdot (\underbrace{1+3+\cdots+3^{\lfloor \log_{3}n \rfloor}}_{\lfloor \log_{3}n \rfloor + 1})\\
&= c\cdot\frac{1\cdot(1-3^{\lfloor \log_{3}n \rfloor + 1})}{1-3}\\
&= c' \cdot (3^{\lfloor \log_{3}n \rfloor})\\
&\leq c' \cdot n \in O(n)
\end{align*}$$
除此之外, `createstack` 需要 $O(1)$, `isFullStack` 需要 $O(1)$,因為這些操作沒有考慮到 `m` [^stack].
值得一提的概念是 **amortized analysis**, 在絕大多情況下, `enlarge` 很少發生. 因此對於單次操作而言, 只要沒有遇上 $3$ 的指數的限制下, 就會是 $O(1)$. 然而 $O$ 是最糟情況, 這個問題的複雜度為 $O(n)_{\#}$.
## Problem 3 - Array / Linked Lists (60pts)
### 1. (15pts)
```julia {.line-numbers}
function is_jumping_forever(A, i_init)
A_record = fill(False, length(A)) # Initiate record array with False
i = i_init
is_forever = False
while (A_record[i] != True)
if (A[i] == i)
break
else
A_record[i] = True
i = A[i]
end
end
is_forever = (A_record[i] == True) ? True : False
return is_forever
end
end
```
**Workflow**
只要
1. 展開一條一樣長度的 Array, 全部填滿 `False`
2. `While loop` :
1. 從起始點開始走, 如果遇到 `A[i]=i` 就跳出
2. 如果 `A[i] != i`, 紀錄走過的地方在 `A_record[i]` 標記 `True`
- 每次迴圈開始要件, 不能走過被標記過的點
3. 回傳: 如果最後一步是走過的, 代表已達終點; 反之, 如果最後一步是 `True` 則代表會 jump forever.
**Time and Space Complexity**
- Time complexity
- $O(n)$: 最糟情況是跳到全部的 elements 才回來, 也就是 `while loop` 執行了 `n` 次
- Space complexity
- $\Theta(n)$: 使用了一樣長度的 array 和固定大小的 temporary variables.
### 2. (15pts)
```julia {.line-numbers}
function loop_size(A, i_init)
A_record = fill(-1, length(A)) # Initiate record array with -1
i = i_init
num_step = 0
is_forever = False
while (A_record[i] == -1)
A_record[i] = num_step
i = A[i]
num_step+=1
end
loop_size = num_step - A_record[i]
return loop_size
end
end
```
**Workflow**
1. 建立一個 `record array`。預設值為-1
2. 迴圈: 每走一步, 紀錄當下的步數在 `record array`
3. 如果遇到的點已經被記錄, 跳出迴圈
4. 將當下的步數減掉上次來的步數, 得到 `loop` 的長度

**Time and Space Complexity**
- Time Complexity
- $O(n)$: 需要走完一次的迴圈, 迴圈最大可以是 array 的長度 `n`
- Space complexity
- $\Theta(n)$: 需要產生一樣長度的 `record array` 長度為 `n`, 再加上其他常數記憶體用量.
### 3. (15pts)
**分析**
因為是求中位數, 可以知道
$$\begin{align*}
\max(M_{0,i}, M_{i,j}, M_{j,n}) &= M_{j,n} \\
\min(M_{0,i}, M_{i,j}, M_{j,n}) &= M_{0,i}
\end{align*}$$
所以 $M_{i,j}$ 越小越好, 也就是 $\min~f$ 發生在 $j=i+1$ 時, 接下來要尋找的就是在 $n-3$ 的空間中找到最適當的斷點. 接著使用 線性 尋找 $O(n)$, 朝 minimium 前進. 記憶體佔用為 $O(h)$ 用來存放 array. 使用 array 查找 median 為常數時間.
```julia {.line-numbers}
# Find i, j
function GetMedian(arr, s, t)
if (s+t)/2 is odd
return arr[(s+t)/2]
else
return (arr[s+t/2] + arr[s+t/2 + 1])/2
end
end
function find_ij(arr)
f = maximum(arr) # default value of f
I = 0 # default i
for i in 1 : (length(arr)-1 - 2) #last three elements
m0 = GetMedian(arr, 0, i)
m1 = arr[i+1] #minimum length of [i,j)
m2 = GetMedian(arr, i+2, length(arr)-1)
f_candidate = m2 - m0
if (f_candidate < f)
f = f_candidate
I = i #update new I
end
end
return I, I+1 # i, j
end
```
### 4. (15pts) Circular linked list
假設一個遞增序列
$$a_{n}\rightarrow a_{n-1}\rightarrow \cdots\rightarrow a_{1}$$
滿足 $a_{i} < a_{j}$ 當 $i<j$. 因為排序好的序列在 circular linked list 會使 $a_{1} \rightarrow a_{n}$ 產生一個 decreasing node. 將 circular linked list 遞增排序的步驟:
1. 找到 decreasing nodes ($O(n)$), 把他們從 circular 切出來, 將 previous node 和 next node 連起來 ($O(1)$)
2. 找到 minimum bigger value ($O(n)$), 將數字切進去 ($O(1)$)
3. 如果沒有 bigger value, 找到 minimum value 連起來
```julia {.line-numbers}
function insertSort(head, value)
node1 = head
node2 = node1.next
node3 = node1.next.next
val = value
while (node2 != head)
if (node2.)
#move next
node1 = node1.next
node2 = node2.next
ndoe3 = node3.next
end
end
function make_sorted(head::node)
node1 = head
node2 = node1.next
node3 = node1.next.next
decNodes = []
while (node2 != head)
if (node2.value > node3.value)
link(node1 -> node3)
append!(decNodes, node2)
if (length(decNodes) == 2)
break
end
end
#move next
node1 = node1.next
node2 = node2.next
ndoe3 = node3.next
end
#rewiring
nodeA, nodeB = decNodes
#find new cite to wire
end
```
[^master]: Time complexity of recursive functions [Master theorem]. https://yourbasic.org/algorithms/time-complexity-recursive-functions/
[^ITA_theta]: Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (n.d.). Introduction to Algorithms, Third Edition. **pp.44-47**
[^prop_o]: Properties of Big-O. Data Structures and Algorithms with Object-Oriented Design Patterns in Java. [[Link](https://book.huihoo.com/data-structures-and-algorithms-with-object-oriented-design-patterns-in-java/html/page62.html)]
[^log]: $b^{\log_b p} = p$. see [log equalities](https://ducdoan.com/wp-content/uploads/2018/12/2.jpg).
[^ITA_1154]: Introduction to Algorithms, Third Edition. **pp.1154-1156**.
[^log]: How to prove $\log n! = \Theta(n\log n)$. [[tutorial](http://www.mcs.sdsmt.edu/ecorwin/cs372/handouts/theta_n_factorial.htm)]
[^master_tree]: Introduction to Algorithms, Third Edition. Chapter 4 Divide-and-Conquer. **pp.104**
[^xpx]: $\lim_{n\rightarrow \infty} \frac{log(n^n/2)}{log(n^{n/2})} = 2$. Becuase both nominator and denominator approach to infinity as $n \rightarrow \infty$. We can use [L'Hospital's Rule](https://en.wikipedia.org/wiki/L%27H%C3%B4pital%27s_rule). $\lim_{n\rightarrow \infty} \frac{log(n^n/2)}{log(n^{n/2})} = \lim_{n\rightarrow \infty} \frac{log(n^n/2)'}{log(n^{n/2})'}$. Later on, $\frac{d \log(n^n/2)}{dn} = \frac{1}{2}\log(n^n)'$[^lhos]; $\frac{d \log(\frac{n^n}{2})}{dn} = \log(n^n)'$ . Therefore, $\lim_{n\rightarrow \infty} \frac{log(n^n/2)}{log(n^{n/2})} = 2$
[^lhos]: $(x^x)' = x^x (\log(x) + 1)$. [[proof](https://jakubmarian.com/derivative-of-xx/)]
[^gcd]: Euclidean algorithm. [[link](https://codility.com/media/train/10-Gcd.pdf)]
[^gcd2]: Euclidean algorithm for computing the greatest common divisor. [[link](https://cp-algorithms.com/algebra/euclid-algorithm.html)]
[^gcd3]: Lame's Theorem. [[link](https://www.nitt.edu/home/academics/departments/cse/faculty/kvi/Euclidean%20algorithm%20for%20gcd.pdf)]
[^queueD]: Design diagram of reversing a queue with a temporary queue. Noted that the initial queue contains `[3,2,1]` with `1` at the front.
<img src="./reverse_queue.png" height=1000>
[^dymarray]: Dynamic Array Amortized Analysis. [[link](https://www.interviewcake.com/concept/java/dynamic-array-amortized-analysis)]
[^stack]: Implementation of a dynamic stack
```c {.line-numbers}
struct Stack {
int top;
int capacity;
int *arr;
}
struct Stack *createStack() { // O(1)
struct Stack *S = malloc(sizeof(struct Stack));
S->capacity = 1;
S->top = -1;
S->arr = (int*)malloc(S->capacity * sizeof(int));
return S;
}
int isFullStack(struct Stack *S) { // O(1)
return (S->top == S->capacity-1);
}
void enlarge(struct Stack *S) { // O(3*m)
int new_capacity = (S->capacity * 3);
S->capacity = new_capacity;
S->arr = (int*)realloc(S->arr, new_capacity * sizeof(int));
}
void push(struct Stack *S, int data) {
S->arr[++S->top] = data; // O(1)
if (isFullStack(S))
enlarge(S);
}
```
[^fib]: 費波那契數列. [[PDF](http://www.hwsh.tc.edu.tw/ischool/public/resource_view/open.php?file=b38c393be59da9b7e5acb45bde07b8c7.pdf)]