Arvin Liu
感受一下?
給你一個數字 N,請輸出 N!
那還不簡單?
不就是基礎 for 迴圈嗎?
來想想看有沒有其他寫法!
int factorial(int N) {
int ans = 1;
for (int i=1; i<=N; i++)
ans *= i;
return ans;
}
C++
def factorial(N):
ans = 1
for i in range(1, N):
ans *= i
return ans
Python
給你一個數字 N,請輸出 N!
假設現在有一個函數:
如果不用 "!" 該怎麼寫?
給你一個數字 N,請輸出 N!
直接把它轉成程式?
跑跑看!
int factorial(int N) {
return N * factorial(N-1);
}
def factorial(N):
return N * factorial(N-1)
C++
Python
輸出結果 :
4
3
2
1
0
-1
-2
....
跑不完!
這裡就該停了!
#include <stdio.h>
int factorial(int N) {
printf("%d\n", N);
return N * factorial(N-1);
}
int main() {
printf("%d", factorial(4));
}
def factorial(N):
print(N)
return N * factorial(N-1)
print(factorial(4))
C++
Python
給你一個數字 N,請輸出 N!
這樣就可以了!
int factorial(int N) {
if (N == 0) return 1;
return N * factorial(N-1);
}
def factorial(N):
if N == 0: return 1
return N * factorial(N-1)
C++
Python
以 作為舉例
呼叫
呼叫
呼叫
回傳
回傳
回傳
回傳
實際例子可以參考另一個投影片
按按看(?)
東東爬階梯每次可以走一或兩階。
假設階梯有 n 階,那有幾種走法?
🐦
🐦
3 階有 3 種走法
4 階有 5 種走法
1+1+1+1
1+1+2
1+2+1
2+1+1
2+2
1+1+1
1+2
2+1
有點難?
f(n) = 走 n 階的可能數
f(n-1) f(n-2)
🐦
...
+
東東爬階梯每次可以走一或兩階。
假設階梯有 n 階,那有幾種走法?
f(n) = 走 n 階的可能數
f(n-1) f(n-2)
+
輸入可能是 1 以後的正整數。
f(1) 可以用遞迴算出嗎?
f(2) 可以用遞迴算出嗎?
f(3) 可以用遞迴算出嗎?
⭕
❌
❌
f(1) = 1, f(2) = 2
東東爬階梯每次可以走一或兩階。
假設階梯有 n 階,那有幾種走法?
f(n) = 走 n 階的可能數
f(n-1) f(n-2)
+
f(1) = 1, f(2) = 2
東東爬階梯每次可以走一或兩階。
假設階梯有 n 階,那有幾種走法?
int fib(int N) {
if (N == 1) return 1;
if (N == 2) return 2;
return fib(N-1) + fib(N-2);
}
def fib(N):
if N == 1: return 1
if N == 2: return 2
return fib(N-1) + fib(N-2)
C++
Python
這就是鼎鼎大名的費波那契數列
fibonacci
以 作為舉例
2
1
3
2
請問從 N 顆不同的球裡,
有幾種取出 M 顆球的方法?
有 4 顆相異的球
如果取三顆,共 4 種取法
如果取兩顆,共 6 種取法
請問從 N 顆不同的球裡,
有幾種取出 M 顆球的方法?
有 4 顆相異的球
如果取三顆,共 4 種取法
這個也好難....
C(n, m) = N 取 M 的可能數
請問從 N 顆不同的球裡,
有幾種取出 M 顆球的方法?
有 4 顆相異的球
如果取三顆,共 4 種取法
取了最
後的球
沒取最
後的球
取了 後
需要再從三顆中取兩顆
放棄 後
需要再從三顆中取三顆
C(n, m) = N 取 M 的可能數
❌
請問從 N 顆不同的球裡,
有幾種取出 M 顆球的方法?
有 n 顆相異的球
想要取 m 顆
取了最
後的球
沒取最
後的球
❌
...
還剩m顆要選
還剩m-1顆要選
C(n-1, m)
C(n-1, m-1)
+
C(n, m) = N 取 M 的可能數
放棄 後
需要再從n-1顆中取m顆
取了 後
需要再從n-1顆中取m-1顆
請問從 N 顆不同的球裡,
有幾種取出 M 顆球的方法?
f(N,M) = N 取 M 的可能數
C(n-1, m)
C(n-1, m-1)
+
C(3, 2)
C(2, 1)
C(2, 2)
C(1, 0)
C(1, 1)
n 跟 m 減到哪裡該停?
想想看 C(3, 2) 的例子吧!
m = 0 或者 n = m 時為1
請問從 N 顆不同的球裡,
有幾種取出 M 顆球的方法?
f(N,M) = N 取 M 的可能數
C(n-1, m)
C(n-1, m-1)
+
m = 0 或者 n = m 時為1
一個看似複雜的題目,遞迴程式卻非常少!
這就是遞迴的魅力之處 (?)
int C(int n, int m){
if (m == 0 || m == n) return 1;
return C(n-1, m-1) + C(n-1, m);
}
def C(n, m):
if m == 0 or m == n: return 1
return C(n-1, m) + C(n-1, m-1)
C++
Python
給你 N 個物品和物品的重量和價值,以及祖靈的背包的重量限制。
請問祖靈最多可以帶價值東西回家?
拿走什麼? | 總重 | 價值 |
---|---|---|
G+C+S | 15kg | $8 |
Y+C+S+B | 8kg | $15 |
... | ... | ... |
C
B
Y
G
S
給你 N 個物品和物品的重量和價值,以及祖靈的背包的重量限制。
請問祖靈最多可以帶價值東西回家?
f(n, W) = 前 n 個物品中,
限重 W 的情況下的最大價值
有點複雜... 試試看裸套題目定義
f(2, 5): 如果可以拿0~2號的物品,
背包限重為5,回傳所有可能中的的最大價值。
f(7, 10): 如果可以拿0~7號的物品,
背包限重為10,回傳所有可能中的最大價值。
給你 N 個物品和物品的重量和價值,以及祖靈的背包的重量限制。
請問祖靈最多可以帶價值東西回家?
又卡住了 :( ...
其實背包問題跟 C(n,m) 很像!
都是 n 個物品裡面挑幾個東西出來
f(n, W) = 前 n 個物品中,
限重 W 的情況下的最大價值
給你 N 個物品和物品的重量和價值,以及祖靈的背包的重量限制。
請問祖靈最多可以帶價值東西回家?
又卡住了 :( ...
f(n, W) = 前 n 個物品中,
限重 W 的情況下的最大價值
有 n 顆相異的物品,
限重 W
取了最
後的
沒取最
後的
❌
...
💎
👑
📿
💎
💎
0/1 背包問題
有 n 顆相異的球
想要取 m 顆
取了最
後的球
沒取最
後的球
❌
...
C(n, m) 問題
給你 N 個物品和物品的重量和價值,以及祖靈的背包的重量限制。
請問祖靈最多可以帶價值東西回家?
有 n 顆相異的物品,
限重 W
取了最
後的
沒取最
後的
❌
...
💎
👑
📿
💎
💎
放棄第 n 個物品,
當它不存在。
可以裝的重量剩
但多了價值
f(n, W) = 前 n 個物品中,
限重 W 的情況下的最大價值
背包還剩
背包還剩
給你 N 個物品和物品的重量和價值,以及祖靈的背包的重量限制。
請問祖靈最多可以帶價值東西回家?
f(n, W) = 前 n 個物品中,
限重 W 的情況下的最大價值
n 會一直往下減... 考慮第零號物品!
f(n, W) = 前 n 個物品中,限重 W 的情況下的最大價值
int f(int n, int w) {
if (n == 0)
return w >= W[n] ? V[n] : 0;
if (w >= W[n])
return max(
f(n-1, w),
f(n-1, w - W[n]) + V[n]
);
return f(n-1, w);
}
def f(n, w):
if n == 0:
return V[n] if w >= W[n] else 0
if w >= W[n]:
return max(
f(n-1, w),
f(n-1, w-W[n]) + V[n]
);
return f(n-1, w);
C++
Python
那麼你會寫出遞迴式嗎?
我們以爬樓梯的題目來看
如何避免重算?
想像有個函數...
輸入 A
輸入 B
輸入 A
第一次碰到!
算出f(A)
第一次碰到!
算出f(B)
算過 A 了!
拿出之前算過的答案
你必須在第一次碰到的時候記錄起來!
用一個方法可以記錄 「問題」 -> 「答案」
例如 陣列、unordered_map、dict 等等
long long dp[1000];
long long fib(int n){
if (n == 0 || n == 1)
return 1;
if (!dp[n])
dp[n] = fib(n-1) + fib(n-2);
return dp[n];
}
dp = {}
def fib(n):
if n == 0 or n == 1 :
return 1
if n not in dp:
dp[n] = fib(n-1) + fib(n-2)
return dp[n]
C++
Python
因為 fib 都會 > 0,所以我們可以用
dp[n] 是不是 0 來決定有沒有算過。
來分析看看複雜度吧!
重複計算 (原本的code)
不重複計算 (記憶化)
f(n) = f(n-1) + f(n-2)
"大概"每多一個n,
就會多算一倍
算完 f(n-1) 時,
f(n-2) 已經算過了,不用重算
差超多
long long dp[1000];
long long f(int n){
if (n == 0 || n == 1)
return 1;
if (!dp[n])
dp[n] = f(n-1) + f(n-2);
return dp[n];
}
來用人腦跑一次看看吧!如果呼叫 f(4)...
n | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
f(n) | 1 | 1 | ? | ? | ? |
- | f(1) | f(2) | f(3) | ||
- | f(0) | f(1) | f(2) |
1
1
2
2
1
3
3
2
5
f(2) 不用再重新遞迴一次 f(1) 跟 f(0) 了!
不是指某個回答問題的網站。
在函式裡宣告的變數都會在stack空間。 (包括main)
當你呼叫非常非常非常多的函式...
stack
警戒線
超出stack警戒線就會導致程式執行錯誤。
(所以是Runtime Error)
stack
警戒線
extern int main2(void) __asm__ ("main2");
int main2() {
run();
exit(0);
}
int main() {
// 跟heap借256MB
int size = 256 << 20;
char *p = (char *)malloc(size) + size;
__asm__ __volatile__(
"movq %0, %%rsp\n"
"pushq $exit\n"
"jmp main2\n"
:: "r"(p));
}
#include <cstdlib>
Euclidean Algorithm
在猥瑣罐頭下樓梯(zj a272) 中,
除了要寫東東爬樓梯以外,
多了一個奇怪的條件...
你可能會輸出
但在運算的過程中數字太大噴掉...
在每次運算完後直接%就可以避免溢位!
想想看程式要怎麼寫吧!
int GCD(int a, int b){
if(b == 0)
return a + b;
return GCD(b, a % b);
}
!數學警告!
假設你學會了時間複雜度...
來上點難度吧!
def f(n):
if n == 0:
return
f(n-1)
這段程式碼的時間分析大概就長這樣
假設你學會了時間複雜度...
來上點難度吧!
來更難的吧!
這種函式的程式碼大概長這樣
f(l, r):
if l+1 == r:
return
m = (l + r) / 2
f(l, m)
f(m, r)
給定一個數列 <A_n>,
請問有個對 (i, j) 滿足以下條件:
....?
這題老實說直接想是很難想出來的。
我們先來亂拆看看吧!
1 | 5 | 2 | 7 | 0 | 9 | 3 | 8 | 4 | 6 |
---|
A
f(n) = f(n-1) + A_n 跟前面數字的逆序數對
跟裸做一樣😭
1 | 5 | 2 | 7 | 0 | 9 | 3 | 8 | 4 | 6 |
---|
A
如果每次切一半有辦法更好嗎...?
如果我們考慮左邊跟右邊,
那麼逆序數對會有三種可能:
怎麼定義函式?
1 | 5 | 2 | 7 | 0 | 9 | 3 | 8 | 4 | 6 |
---|
A
暴力搜尋兩側逆對:
想這麼多還是跟裸做一樣😭
想這麼多還是跟裸做一樣😭
等等!如果兩側都是排序好的呢?
0 | 1 | 2 | 5 | 7 |
---|
3 | 4 | 6 | 8 | 9 |
---|
L
R
你想得出來怎麼更有效率的做嗎?
想這麼多還是跟裸做一樣😭
等等!如果兩側都是排序好的呢?
0 | 1 | 2 | 5 | 7 |
---|
3 | 4 | 6 | 8 | 9 |
---|
L
R
從 L 的每個元素看:
我們目標是要找到 R 有幾個元素比 L 還要小
二分搜!
好像很有機會喔?
想這麼多還是跟裸做一樣😭
等等!如果兩側都是排序好的呢?
def f(l, r):
想這麼多還是跟裸做一樣😭
等等!如果兩側都是排序好的呢?
0 | 1 | 2 | 5 | 7 |
---|
3 | 4 | 6 | 8 | 9 |
---|
L
R
再等等!
好像有更好的算法!
你會發現指到的地方
永遠是遞增的!
→雙指針
等等!如果兩側都是排序好的呢?
0 | 1 | 2 | 5 | 7 |
---|
3 | 4 | 6 | 8 | 9 |
---|
L
R
→雙指針
l
r
r
r
r
l
l
l
l
+0
+0
+0
+2
+3
def f(l, r):
如果你做 Merge Sort 就不用排序
APCS 不會考,純介紹性質 :)
實作 A * B,
但數字很大。
小學直式乘法!
陣列 A
陣列 B
陣列 C
好慢...
實作 A * B,
但數字很大。
嘗試切一半!
A_L
A_R
B_R
B_L
A_R * B_R
A_L * B_R
A_R * B_L
A_L * B_L
實作 A * B,
但數字很大。
嘗試切一半!
在做 A*B 的大數乘法的時候,
會需要做四次的一半的乘法
跟裸做一樣😭
實作 A * B,
但數字很大。
A_L
A_R
B_R
B_L
A_R * B_R
A_L * B_R
A_R * B_L
A_L * B_L
四次乘法,一次加法→兩次加法,三次乘法,兩次減法
實作 A * B,
但數字很大。
四次乘法,一次加法→兩次加法,三次乘法,兩次減法
A_R * B_R
A_L * B_R
A_R * B_L
A_L * B_L