coherent17
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
2
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# Dynamic Programming [TOC] ## Introduction 為一種解題方法,可以將時間複雜度為exponential time$O(c^n)$優化成polynomial time$O(n^c)$甚至是linear time$O(n)$。那是甚麼樣的問題可以使用dynamic programming呢?要有以下兩種特性的題目才能夠使用dynamic programming的方法來加速。 * 1. Optimal Substructure(最佳子結構性質) We can get optimal solution to the problem by combining the optimal solution by the subproblem. * 2. Overlapping Subproblems(子問題重疊性質) If you solve the same subproblems many times, then the overlapping property also present. ## How to recognize the DP problem * Combinatorial(Count something) * How many ways to make a change? * How many ways to traverse a graph? * How many steps needed to get from point A to point B? * Optimization(Minimize or maximize the function) * What is the minimum numbers of steps needed to get from point A to point B? * What is the maximum profit gained by buying and selling a stock? ## Partial Sum problem Find the sum of the first n number in nature number. \begin{gather*}1 + 2 + 3 + 4 +...+ n = \displaystyle\sum_{n=1}^{n} n\end{gather*} 假設我們沒有學過累加的數學運算,不知道可以透過下式來計算 \begin{gather*}\frac{n \cdot (n+1)}{2}\end{gather*} 推導關係式: 定義$F(n)$為從$1+2+...+n$的結果 可以得知: \begin{gather*} F(1) = 1\end{gather*} \begin{gather*} F(2) = F(1) + 2 = 3\end{gather*} \begin{gather*} F(3) = F(2) + 3 = 6\end{gather*} \begin{gather*} F(n) = F(n-1) + n\end{gather*} $F(2)$可以經由$F(1)$獲得,$F(3)$可以經由$F(2)$獲得,而$F(n)$則是從$F(n-1)$獲得。這就是dynamic programming的核心概念,不讓過去已經算出來的東西白算,而是透過之前已知的結果再往下延伸。使用額外的記憶體去儲存這些結果以換來較少的預算時間。 code 如下: ```c= #include <stdio.h> #include <stdlib.h> int n_Sum(int n){ int *dp = malloc(sizeof(int) *(n + 1)); //base case: dp[0] = 0; for (int i = 1; i <= n;i++){ dp[i] = dp[i - 1] + i; } int result = dp[n]; free(dp); return result; } //using math formula int n_SumTest(int n){ return n * (n + 1) / 2; } int main(){ printf("%d %d\n", n_Sum(0), n_SumTest(0)); printf("%d %d\n", n_Sum(1), n_SumTest(1)); printf("%d %d\n", n_Sum(2), n_SumTest(2)); printf("%d %d\n", n_Sum(3), n_SumTest(3)); printf("%d %d\n", n_Sum(4), n_SumTest(4)); printf("%d %d\n", n_Sum(5), n_SumTest(5)); printf("%d %d\n", n_Sum(100), n_SumTest(100)); return 0; } ``` ## Climbing Stair Problem(Combinatorial Problem) You are climbing a staircase. It takes n steps to reach the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? ![](https://i.imgur.com/toVtEKp.png) 推導關係式: 定義$F(i)$為爬到第$i$層樓梯所有可能的方法數。 接著,將問題分成許多個小問題,一一擊破,並且考慮base case的邊界條件問題。 * 0 stair * doing nothing(it still count for a method) $\rightarrow F(0) = 1$ * 1 stair * 1 $\rightarrow F(1) = 1$ * 2 stairs * 1,1 * 2 $\rightarrow F(2) = 2$ 假如我現在要到第$n$階的樓梯,因為一次只能爬一階或是兩階,所以我所在的前一個 位置可能是在第$n-1$階的樓梯或是第$n-2$階的樓梯,並沒有其他的可能性。如此一來,爬到第$n$階的樓梯的方法數為:爬到第$n-1$階及第$n-2$階樓梯的方法數的總和,因此可以列出下式: \begin{gather*} F(n) = F(n-1) + F(n-2)\end{gather*} 剛好列出來的式子就是大名鼎鼎的費氏數列(Fibonacci sequence)。 code 如下: ```c= #include <stdio.h> #include <stdlib.h> //time complexity: O(n), space complexity: O(n) int climbStairs(int n){ int *dp = malloc(sizeof(int) * (n + 1)); //base case dp[0] = 1; dp[1] = 1; //inductive to find answer for (int i = 2; i <= n;i++){ dp[i] = dp[i - 1] + dp[i - 2]; } int result = dp[n]; free(dp); return result; } int main(){ printf("%d \n", climbStairs(0)); printf("%d \n", climbStairs(1)); printf("%d \n", climbStairs(2)); printf("%d \n", climbStairs(3)); printf("%d \n", climbStairs(4)); printf("%d \n", climbStairs(10)); return 0; } ``` 這種寫法,會有一個很致命的缺點,當input的$n$很大時,將會需要allocate很大的記憶體空間來存這些結果,但是事實上,在每次計算時,僅需要取得前一個及前兩個的結果即可,因此只需要兩個變數來記錄就好,不需要花費那麼多的記憶體空間。 因此可以將上面的climbStairs優化如下: ```c= //time complexity: O(n), space complexity: O(1) int climbStairs(int n){ int result = 0; //base case if(n==0 || n==1){ result = 1; return result; } int front = 1; int tail = 1; //inductive to find answer for (int i = 2; i <= n;i++){ result = front + tail; front = tail; tail = result; } return result; } ``` 僅需要去紀錄前兩個結果即可,不需要把所有中間的結果都記下來。 ### Advanced Discussion #### <font size=5>一次可以爬1,2,3,...k階樓梯</font> 原題: You are climbing a staircase. It takes n steps to reach the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? 倘若一次可以一次走1 2 3階的話,那要如何解呢? 一樣定義$F(i)$為走到第$i$階的所有方法數,在能夠走三階的情況下可以跟前面使用一樣的方法推得: \begin{gather*} F(n) = F(n-1) + F(n-2) + F(n-3)\end{gather*} 從上式中,我們可以知道,若要求得$F(3)$的話,會需要3個base case。 Base Case: * $F(0) = 1$ * $F(1) = 1$ * $F(2) = 2$ 有了上述的條件,就可以解出問題了。 code如下: ```c= #include <stdio.h> #include <stdlib.h> //time complexity: O(n), space complexity: O(n) int climbStairs(int n){ int *dp = malloc(sizeof(int) * (n + 1)); //base case dp[0] = 1; dp[1] = 1; dp[2] = 2; //inductive to find answer for (int i = 3; i <= n;i++){ dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]; } int result = dp[n]; free(dp); return result; } int main(){ printf("%d \n", climbStairs(0)); printf("%d \n", climbStairs(1)); printf("%d \n", climbStairs(2)); printf("%d \n", climbStairs(3)); printf("%d \n", climbStairs(5)); printf("%d \n", climbStairs(10)); return 0; } ``` 那假如想要更進一步去計算走到第$n$階樓梯且一次可以選擇走$1,2,3,...,k$步的話,則可以用將其更進一步推廣為: \begin{gather*} F(n) = F(n-1) + F(n-2) + F(n-3) + ... + F(n-k)\end{gather*} 也就是說最少會需要$k$個base cases。 code如下: ```c= #include <stdio.h> #include <stdlib.h> //time complexity: O(nk), space complexity: O(n) [can be optimized to O(k)] //return the number of distinct ways to reach the n-th stair by using 1 to k steps int climbStairs(int n, int k){ int *dp = malloc(sizeof(int) * (n + 1)); //base case dp[0] = 1; dp[1] = 1; //inductive to find answer for (int i = 2; i <= n;i++){ //calculate dp[i] = dp[i - 1] + dp[i - 2] + ... + dp[i - k] dp[i] = 0; for(int j = 1; j<= k; j++){ //edge case: if i < j -> dp[negative number] if(i < j){ continue; } dp[i] += dp[i - j]; } } int result = dp[n]; free(dp); return result; } int main(){ printf("%d \n", climbStairs(3,2)); printf("%d \n", climbStairs(5,2)); printf("%d \n", climbStairs(3,3)); printf("%d \n", climbStairs(5,3)); printf("%d \n", climbStairs(10,3)); return 0; } ``` #### <font size=5>優化Space complexity</font> 但是這邊其實也不需要使用到$n$個空間的array來存,可以用前面一樣對space complexity的優化方法,其實只需要$k$個空間的array來存即可,所以再來改造一下吧!由上面推得的關係式,可以得知其實僅需要$k$個位置來存放即可。示意如下: 假設$n = 5, k = 3$(共有五階樓梯,而一次可以選擇要走一步或是兩步或是三步): 一開始的Base Case: | $i$ | 0 | 1 | 2 | |:-------:| -------- |:--------:|:--------:| | $dp[i]$ | $F(0)=1$ | $F(1)=1$ | $F(2)=2$ | 而接下來要計算踏到第三階的方法數$F(3)=F(2)+F(1)+F(0)$,而在這邊可以發現在之後的運算,已經不會再使用到$F(0)$了,因此將$dp[0]$的位置改為存放$F(3)=2+1+1=4$ | $i$ | 0 | 1 | 2 | |:-------:|:--------:|:--------:|:--------:| | $dp[i]$ | $F(3)=4$ | $F(1)=1$ | $F(2)=2$ | 而接下來要計算踏到第四階的方法數$F(4)=F(3)+F(2)+F(1)$,而在這邊可以發現在之後的運算,已經不會再使用到$F(1)$了,因此將$dp[1]$的位置改為存放$F(4)=4+2+1=7$ | $i$ | 0 | 1 | 2 | |:-------:|:--------:|:--------:|:--------:| | $dp[i]$ | $F(3)=4$ | $F(4)=7$ | $F(2)=2$ | 而接下來要計算踏到第五階的方法數$F(5)=F(4)+F(3)+F(2)$,而在這邊可以發現在之後的運算,已經不會再使用到$F(2)$了,因此將$dp[2]$的位置改為存放$F(5)=7+4+2=13$ | $i$ | 0 | 1 | 2 | |:-------:|:--------:|:--------:|:---------:| | $dp[i]$ | $F(3)=4$ | $F(4)=7$ | $F(5)=13$ | 所以究竟要如何決定要把算出來的$F(i)$放在哪呢?其實將$i\%k$就可以得知要將算出來的結果放在哪邊了,如此一來便可以將Space complexity從$O(n)$降為$O(k)$。 code如下: ```c= #include <stdio.h> #include <stdlib.h> #define printDPMode 1 //time complexity: O(nk), space complexity: O(k) //return the number of distinct ways to reach the n-th stair by using 1 to k steps void print_dp(int *dp, int size){ for(int i = 0; i < size; i++){ printf("%d ", dp[i]); } printf("\n"); } int climbStairs(int n, int k){ int *dp = malloc(sizeof(int) * (n + 1)); //base case dp[0] = 1; //inductive to find answer for (int i = 1; i <= n;i++){ //calculate dp[i] = dp[i - 1] + dp[i - 2] + ... + dp[i - k] dp[i] = 0; //initialize the partial sum to zero for(int j = 1; j< k; j++){ //edge case: if i < j -> dp[negative number] if(i < j){ continue; } dp[i % k] += dp[(i - j)%k]; } //show the dp array at each round if debug mode is true if(printDPMode){ printf("i = %d: ", i); print_dp(dp, k); } } int result = dp[n % k]; free(dp); return result; } int main(){ printf("%d \n", climbStairs(3,2)); printf("%d \n", climbStairs(5,2)); printf("%d \n", climbStairs(3,3)); printf("%d \n", climbStairs(5,3)); printf("%d \n", climbStairs(10,3)); return 0; } ``` #### <font size=5>跳過特定的樓梯不能走</font> 假設$n = 7, k = 3$(共有7階樓梯,而一次可以選擇要走一步或是兩步或是三步),而其中第$1,3,4$階階不能踏上去,否則腳會受傷。 由前面的推導可以得知公式為: \begin{gather*} F(n) = F(n-1) + F(n-2) + F(n-3) + ... + F(n-k)\end{gather*} * 而紅色的index標示的是不能經過的樓梯,因為沒有辦法到該層階梯上,所以$F(1)=F(3)=F(4)=0$。 * 而當$i=0$時,$F(0)=1$。 將目前已知的部分填進dp array中可以得到如下: | $i$ | 0 | <font color="red">1</font> | 2 | <font color="red">3</font> | <font color="red">4</font> | 5 | 6 | 7 | |:------:|:-------------------------------:|:--------------------------:|:---:|:--------------------------:|:--------------------------:|:---:| --- |:---:| | $F(i)$ | **<font color="blue">1</font>** | 0 | | 0 | 0 | | | | * 計算$i=2$時,將其帶入公式可以得到$F(2)=F(1)+F(0)=0+1=1$ | $i$ | 0 | <font color="red">1</font> | 2 | <font color="red">3</font> | <font color="red">4</font> | 5 | 6 | 7 | |:------:|:---:|:--------------------------:|:-------------------------------:|:--------------------------:|:--------------------------:|:---:| --- |:---:| | $F(i)$ | 1 | 0 | **<font color="blue">1</font>** | 0 | 0 | | | | * 計算$i=5$時,將其帶入公式可以得到$F(5)=F(4)+F(3)+F(2)=0+0+1+=1$ | $i$ | 0 | <font color="red">1</font> | 2 | <font color="red">3</font> | <font color="red">4</font> | 5 | 6 | 7 | |:------:|:---:|:--------------------------:|:---:|:--------------------------:|:--------------------------:|:-------------------------------:| --- |:---:| | $F(i)$ | 1 | 0 | 1 | 0 | 0 | **<font color="blue">1</font>** | | | * 計算$i=6$時,將其帶入公式可以得到$F(6)=F(5)+F(4)+F(3)=1+0+0+=1$ | $i$ | 0 | <font color="red">1</font> | 2 | <font color="red">3</font> | <font color="red">4</font> | 5 | 6 | 7 | |:------:|:---:|:--------------------------:|:---:|:--------------------------:|:--------------------------:|:---:|:-------------------------------:|:---:| | $F(i)$ | 1 | 0 | 1 | 0 | 0 | 1 | **<font color="blue">1</font>** | | * 計算$i=7$時,將其帶入公式可以得到$F(7)=F(6)+F(5)+(4)=1+1+0=2$ | $i$ | 0 | <font color="red">1</font> | 2 | <font color="red">3</font> | <font color="red">4</font> | 5 | 6 | 7 | |:------:|:---:|:--------------------------:|:---:|:--------------------------:|:--------------------------:|:---:|:---:|:-------------------------------:| | $F(i)$ | 1 | 0 | 1 | 0 | 0 | 1 | 1 | **<font color="blue">2</font>** | 如此一來便可以計算出答案了。 code如下: ```c= #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #define printDPMode 1 void print_dp(int *dp, int size){ for(int i = 0; i < size; i++){ printf("%d ", dp[i]); } printf("\n"); } int climbStairs(int n, int k, bool *skipStair){ int *dp = malloc(sizeof(int) * (n + 1)); //base case dp[0] = 1; //inductive to find answer for (int i = 1; i <= n;i++){ //calculate dp[i] = dp[i - 1] + dp[i - 2] + ... + dp[i - k] dp[i] = 0; //initialize the partial sum to zero for(int j = 1; j< k; j++){ //edge case: if i < j -> dp[negative number] if(i < j){ continue; } if(skipStair[i - 1]) dp[i % k] = 0; else dp[i % k] += dp[(i - j)%k]; } //show the dp array at each round if debug mode is true if(printDPMode){ printf("i = %d: ", i); print_dp(dp, k); } } int result = dp[n % k]; free(dp); return result; } int main(){ bool skipStair[8] = {false, true, false, true, true, false, false, false}; printf("%d \n", climbStairs(7,3,skipStair)); return 0; } ``` ## Climbing Stair Problem(Optimization Problem) 假設$n = 3, k = 2, cost=[0,3,2,4]$(共有三階樓梯,而一次可以選擇要走一步或是兩步,而走到第一階會被收3塊錢,第二階會被收2塊錢,到第三階會被收4塊錢,找到最少的花費走到第3階的走法。) * 1. $F(i)$: the minimum cost to get to the i-th stair * 2. Base Case: * $F(0) = 0$ * $F(1) = 3$ * $F(2) = 2$ 有兩種走法,先到1再到2,而這樣的花費(5塊錢)是大於直接走兩步直接到第2的花費的(2塊錢) * $F(3) = 6$ 直接跳到第2階,再走一步到第三階 * 3. $F(n)=cost[n]+min(F(n-1),F(n-2))$ code如下: ```c= #include <stdio.h> #include <stdlib.h> int min(int a, int b){ return (a > b) ? b : a; } //time complexity: O(n), Space complexity: O(n) int ClimbStairs_MinCost(int n, int *cost){ int *dp = malloc(sizeof(int) * (n + 1)); //Base case: dp[0] = cost[0]; dp[1] = cost[1]; for(int i = 2; i <= n; i++){ dp[i] = cost[i] + min(dp[i-1], dp[i-2]); } int result = dp[n]; free(dp); return result; } int main(){ int cost[4] = {0,3,2,4}; printf("MinCost = %d\n", ClimbStairs_MinCost(3,cost)); return 0; } ``` ### Advanced Discussion 到這邊已經可以算出最少要花費多少錢才可以走到樓梯的頂端了,但是仍然不知道實際上經過的路徑是什麼,接下來要想辦法紀錄所經過的路徑是什麼。 假設$n = 8, k = 2, cost=[0,3,2,4,6,1,1,5,3]$(共有8階樓梯,而一次可以選擇要走一步或是兩步,而走到不同階層上所要付的錢紀錄在cost array中。) 經由前面的分析可以推得計算的公式為: \begin{gather*} F(n)=cost[n]+min(F(n-1),F(n-2))\end{gather*} 那接下來就可以將其一步一步計算得到結果,那這邊我們會需要使用到兩個array,一個用以計算$F(i)$的數值,而另一個用來記錄抵達該層階梯的前一層為哪一層。 * 紀錄走到該層所需的最少cost為多少。 | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:------:|:---:|:---:| --- | --- | --- | --- | --- |:---:|:---:| | $F(i)$ | | | | | | | | | | * 紀錄走第$i$階的前一個狀態是在哪一層,以方便追溯最終經過的路徑。(假設previous[2]=1,表示抵達第二層的前一個狀態為第一層,以此類推。)先將其全部初始化為-1,以知道哪些樓梯根本不會被經過。 | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:----------:|:---:|:---:| --- |:---:| --- | --- | --- |:---:|:---:| | $previous$ | -1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | * 1. Base Case: * $F(0) = 0$ * $F(1) = 3$ * 而這邊因為若要上到第一層樓梯的最少花費必定來自第零層樓梯,因此將$previous[1]$設為0,已表示在上到第一階樓梯前的狀態是在第零階樓梯。 | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:------:|:---:|:---:| --- | --- | --- | --- | --- |:---:|:---:| | $F(i)$ | 0 | 3 | | | | | | | | | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:----------:|:---:|:--------------------------:| --- |:---:| --- | --- | --- |:---:|:---:| | $previous$ | -1 | <font color="red">0</font> | -1 | -1 | -1 | -1 | -1 | -1 | -1 | * 2. * $F(2) = cost[2] + min(F(1), F(0)) = 2 + min(3,0) = 2 + 0 = 2$ * 而造成第二層樓梯為min cost的狀態為從第零階跳兩層上來,因此將$previous[2]$設為0。 | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:------:|:---:|:---:|:---:| --- | --- | --- | --- |:---:|:---:| | $F(i)$ | 0 | 3 | 2 | | | | | | | | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:----------:|:---:|:---:|:--------------------------:|:---:| --- | --- | --- |:---:|:---:| | $previous$ | -1 | 0 | <font color="red">0</font> | -1 | -1 | -1 | -1 | -1 | -1 | * 3. * $F(3) = cost[3] + min(F(2), F(1)) = 4 + min(2,3) = 4 + 2 = 6$ * 而造成第三層樓梯為min cost的狀態為從第二階跳一層上來,因此將$previous[3]$設為2。 | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:------:|:---:|:---:|:---:|:---:| --- | --- | --- |:---:|:---:| | $F(i)$ | 0 | 3 | 2 | 6 | | | | | | | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:----------:|:---:|:---:|:---:|:--------------------------:| --- | --- | --- |:---:|:---:| | $previous$ | -1 | 0 | 0 | <font color="red">2</font> | -1 | -1 | -1 | -1 | -1 | * 4. * $F(4) = cost[4] + min(F(3), F(2)) = 6 + min(6,2) = 6 + 2 = 8$ * 而造成第四層樓梯為min cost的狀態為從第二階跳兩層上來,因此將$previous[4]$設為2。 | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:------:|:---:|:---:|:---:|:---:|:---:| --- | --- |:---:|:---:| | $F(i)$ | 0 | 3 | 2 | 6 | 8 | | | | | | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:----------:|:---:|:---:|:---:|:---:|:--------------------------:| --- | --- |:---:|:---:| | $previous$ | -1 | 0 | 0 | 2 | <font color="red">2</font> | -1 | -1 | -1 | -1 | * 5. * $F(5) = cost[5] + min(F(4), F(3)) = 1 + min(8,6) = 1 + 6 = 7$ * 而造成第五層樓梯為min cost的狀態為從第三階跳兩層上來,因此將$previous[5]$設為3。 | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:------:|:---:|:---:|:---:|:---:|:---:|:---:| --- |:---:|:---:| | $F(i)$ | 0 | 3 | 2 | 6 | 8 | 7 | | | | | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:----------:|:---:|:---:|:---:|:---:|:---:|:--------------------------:| --- |:---:|:---:| | $previous$ | -1 | 0 | 0 | 2 | 2 | <font color="red">3</font> | -1 | -1 | -1 | * 6. * $F(6) = cost[6] + min(F(5), F(4)) = 1 + min(7,8) = 1 + 7 = 8$ * 而造成第六層樓梯為min cost的狀態為從第五階跳一層上來,因此將$previous[6]$設為5。 | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| | $F(i)$ | 0 | 3 | 2 | 6 | 8 | 7 | 8 | | | | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:----------:|:---:|:---:|:---:|:---:|:---:|:---:|:--------------------------:|:---:|:---:| | $previous$ | -1 | 0 | 0 | 2 | 2 | 3 | <font color="red">5</font> | -1 | -1 | * 7. * $F(7) = cost[7] + min(F(6), F(5)) = 5 + min(8,7) = 5 + 7 = 12$ * 而造成第七層樓梯為min cost的狀態為從第五階跳兩層上來,因此將$previous[7]$設為5。 | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| | $F(i)$ | 0 | 3 | 2 | 6 | 8 | 7 | 8 | 12 | | | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:----------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:--------------------------:|:---:| | $previous$ | -1 | 0 | 0 | 2 | 2 | 3 | 5 | <font color="red">5</font> | -1 | * 8. * $F(8) = cost[8] + min(F(7), F(6)) = 3 + min(12,8) = 3 + 8 = 11$ * 而造成第八層樓梯為min cost的狀態為從第六階跳兩層上來,因此將$previous[8]$設為6。 | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| | $F(i)$ | 0 | 3 | 2 | 6 | 8 | 7 | 8 | 12 | 11 | | $i$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:----------:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:--------------------------:| | $previous$ | -1 | 0 | 0 | 2 | 2 | 3 | 5 | 5 | <font color="red">6</font> | 如次一來,有了previous這個array,便可以從任意的終點往回推經過的路徑為多少。假如終點為要抵達第八階的樓梯,可以往前推得8的前一個為6,6的前一個為5,5的前一個為3,3的前一個為2,2的前一個為0。便得到路徑為: $0 \rightarrow 2 \rightarrow 3 \rightarrow 5 \rightarrow 6 \rightarrow 8$ code 如下: ```c= #include <stdio.h> #include <stdlib.h> void printArray(int *array, int size){ for(int i = 0; i < size; i++){ printf("%d ", array[i]); } printf("\n"); } int min(int a, int b){ return (a > b) ? b : a; } int *getPath(int *previous, int n, int *returnSize){ //count the length of the path (*returnSize) = 0; for(int i = n; i >= 0; i = previous[i]){ (*returnSize)++; } //allocate memory space for path array int *path = malloc(sizeof(int) * (*returnSize)); //put the each stage in path array in reverse order int path_index = (*returnSize)-1; for(int i = n; i >= 0; i = previous[i]){ path[path_index] = i; path_index--; } return path; } int *ClimbStairs_MinCost(int n, int *cost, int *returnSize){ int *dp = malloc(sizeof(int) * (n + 1)); //allocate previous array and initialize it int *previous = malloc(sizeof(int) * (n + 1)); for(int i = 0; i <= n; i++){ previous[i] = -1; } //Base case: dp[0] = cost[0]; dp[1] = cost[1]; //since the first stair must come from the ground previous[1] = 0; for(int i = 2; i <= n; i++){ //calculate the min cost for each stairs dp[i] = cost[i] + min(dp[i-1], dp[i-2]); //update the previous array previous[i] = (dp[i-1] < dp[i-2]) ? (i - 1) : (i - 2); } printf("dp array: "); printArray(dp, n + 1); printf("previous array: "); printArray(previous, n + 1); //print the min cost int result = dp[n]; printf("MinCost = %d\n", result); //get the path by previous array int *path = getPath(previous, n, returnSize); free(dp); free(previous); return path; } int main(){ int returnSize; int cost[9] = {0,3,2,4,6,1,1,5,3}; int *path = ClimbStairs_MinCost(8,cost, &returnSize); printf("path: "); printArray(path, returnSize); free(path); return 0; } ``` ## Unique Paths Problem(Combinatorial Problem) There is a robot on an $m \times n$ grid. The robot is initially located at the top-left corner (i.e., $grid[0][0]$). The robot tries to move to the bottom-right corner (i.e., $grid[m - 1][n - 1]$). The robot can only move either down or right at any point in time. Given the two integers $m$ and $n$, return the number of possible unique paths that the robot can take to reach the bottom-right corner. ![](https://i.imgur.com/Nus5Dhk.png) 簡單來說就是從左上要走到右下,每次只能選擇往又或是往下,看從起點到終點有多少走法。 推導關係式: 因為座標是二維空間的,因此需要兩個變數才能定出所在的方格的位置。 * 定義$F(i,j)$為所有抵達$(i,j)$這個方格的方法數。 * Base Cases: * 假如給定的grid為$1 \times 1$,則自己就是終點,所以算為一種方法數,因此$F(0,0) = 1$ * 假如給定的grid為$2 \times 2$,則從$(0,0)$抵達$(1,1)$有兩種方法,先右再下或是先下再右,因此$F(1,1) = 2$ * 關係式:假如我現在$(i,j)$的位置,那它的來源必為該格的往上一格$(i-1,j)$或是往左一格$(i,j-1)$,因此可以推得下式: \begin{gather*} F(i,j) = F(i-1,j) + F(i,j-1) \end{gather*} 要特別注意哪些的$(i,j)$是不能直接套用$F(i,j) = F(i-1,j) + F(i,j-1)$ * 1. Base Case: $(0,0)$ * 2. 第一行$(i=0,j>0)$的時候,沒有更上面的格子可以到達這樣的$(i,j)$ * 3. 第一列$(j=0,i>0)$的時候,沒有更左邊的格子可以到達這樣的$(i,j)$ * 4. 其餘的直接使用$F(i,j) = F(i-1,j) + F(i,j-1)$即可 code如下: ```c= #include <stdio.h> #include <stdlib.h> void freeMemory(int **array, int m){ for(int i = 0; i < m; i++){ free(array[i]); } free(array); } int UniquePaths(int m, int n){ //allocate 2D array int **dp = malloc(sizeof(int *) * m); for(int i = 0; i < m; i++){ dp[i] = malloc(sizeof(int) * n); } //Base cases: dp[0][0] = 1; //Inductive for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ //skip the base case if(i==0 && j==0) continue; //edge case if i > 0, j = 0, only top block can go to (i,j) else if(i > 0 && j==0) dp[i][j] = dp[i-1][j]; //edge case if i = 0, j > 0, only left block can go to (i,j) else if(i==0 && j > 0) dp[i][j] = dp[i][j-1]; //F(i,j) = F(i-1,j) + F(i,j-1) else dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } int result = dp[m-1][n-1]; freeMemory(dp,m); return result; } int main(){ printf("%d \n", UniquePaths(1,1)); printf("%d \n", UniquePaths(3,4)); printf("%d \n", UniquePaths(3,7)); return 0; } ``` ### Advanced Discussion #### <font size=5> 之中有些格子是不能經過的 </font> A robot is located at the top-left corner of $m \times n$ grid (marked 'Start' in the diagram below). The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below). Now consider if some obstacles are added to the grids. How many unique paths would there be? An obstacle and space is marked as 1 and 0 respectively in the grid. ![](https://i.imgur.com/2bhmMkq.png) 那因為不能抵達那些有障礙物的格子,因此將該格的$F(i,j)$設為0,便能夠解決此問題。 code如下: ```c= #include <stdio.h> #include <stdlib.h> void freeMemory(int **array, int m){ for(int i = 0; i < m; i++){ free(array[i]); } free(array); } int **constructGrid(int m, int n){ int **grid = malloc(sizeof(int *) * m); for(int i = 0; i < m; i++){ grid[i] = malloc(sizeof(int) * n); } //initialize to zero for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ grid[i][j] = 0; } } return grid; } int uniquePathsWithObstacles(int** grid, int m, int n){ //allocate 2D array int **dp = malloc(sizeof(int *) * m); for(int i = 0; i < m; i++){ dp[i] = malloc(sizeof(int) * n); } //Base cases: dp[0][0] = 1; //Inductive for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ //if there exist a obstacle in grid[i][j] if(grid[i][j] == 1){ dp[i][j] = 0; continue; } //skip the base case if(i==0 && j==0) continue; //edge case if i > 0, j = 0, only top block can go to (i,j) else if(i > 0 && j==0) dp[i][j] = dp[i-1][j]; //edge case if i = 0, j > 0, only left block can go to (i,j) else if(i==0 && j > 0) dp[i][j] = dp[i][j-1]; //F(i,j) = F(i-1,j) + F(i,j-1) else dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } int result = dp[m-1][n-1]; freeMemory(dp,m); return result; } int main(){ int **grid1 = constructGrid(3,3); int **grid2 = constructGrid(2,2); int **grid3 = constructGrid(1,1); int **grid4 = constructGrid(3,4); //set the obstacle grid1[1][1] = 1; grid2[0][1] = 1; grid3[0][0] = 1; grid4[1][2] = 1; grid4[1][3] = 1; printf("%d\n", uniquePathsWithObstacles(grid1, 3, 3)); printf("%d\n", uniquePathsWithObstacles(grid2, 2, 2)); printf("%d\n", uniquePathsWithObstacles(grid3, 1, 1)); printf("%d\n", uniquePathsWithObstacles(grid4, 3, 4)); freeMemory(grid1,3); freeMemory(grid2,2); freeMemory(grid3,1); freeMemory(grid4,3); return 0; } ``` ## Unique Paths Problem(Optimization Problem) 現在的grid存的是經過該格能夠拿到的錢錢,每個格子經過都可以賺到相對應的錢,如何走才能得到最大獲利 * 關係式:假如我現在$(i,j)$的位置,那它的來源必為該格的往上一格$(i-1,j)$或是往左一格$(i,j-1)$,要選多的那邊走,再加上踏到$(i,j)$的獲利,因此可以推得下式: \begin{gather*} F(i,j) = grid[i][j] + max(F(i-1,j) + F(i,j-1)) \end{gather*} code如下: ```c= #include <stdio.h> #include <stdlib.h> int max(int a, int b){ return a > b ? a : b; } void freeMemory(int **array, int m){ for(int i = 0; i < m; i++){ free(array[i]); } free(array); } int **constructGrid(int m, int n){ int **grid = malloc(sizeof(int *) * m); for(int i = 0; i < m; i++){ grid[i] = malloc(sizeof(int) * n); } return grid; } int uniquePathsMaxProfit(int** grid, int m, int n){ //allocate 2D array int **dp = malloc(sizeof(int *) * m); for(int i = 0; i < m; i++){ dp[i] = malloc(sizeof(int) * n); } dp[0][0] = 0; //Inductive for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ //skip the base case if(i==0 && j==0) continue; //edge case if i > 0, j = 0, only top block can go to (i,j) else if(i > 0 && j==0) dp[i][j] = grid[i][j] + dp[i-1][j]; //edge case if i = 0, j > 0, only left block can go to (i,j) else if(i==0 && j > 0) dp[i][j] = grid[i][j] + dp[i][j-1]; //F(i,j) = grid[i][j] + max(F(i-1,j) , F(i,j-1)) else dp[i][j] = grid[i][j] + max(dp[i-1][j] , dp[i][j-1]); } } int result = dp[m-1][n-1]; freeMemory(dp,m); return result; } int main(){ int **grid = constructGrid(3,4); //set the profit grid[0][0] = 0, grid[0][1] = 2, grid[0][2] = 2, grid[0][3] = 1; grid[1][0] = 3, grid[1][1] = 1, grid[1][2] = 1, grid[1][3] = 1; grid[2][0] = 4, grid[2][1] = 4, grid[2][2] = 2, grid[2][3] = 0; printf("%d\n", uniquePathsMaxProfit(grid, 3, 4)); //reset the grid grid[0][0] = 0, grid[0][1] = 2, grid[0][2] = 2, grid[0][3] = 50; grid[1][0] = 3, grid[1][1] = 1, grid[1][2] = 1, grid[1][3] = 100; grid[2][0] = 4, grid[2][1] = 4, grid[2][2] = 2, grid[2][3] = 0; printf("%d\n", uniquePathsMaxProfit(grid, 3, 4)); freeMemory(grid,3); return 0; } ``` ### Advanced Discussion 到這邊已經可以算出最大可以獲得多少錢了,但是仍然不知道實際上經過的路徑是什麼,接下來要想辦法紀錄所經過的路徑是什麼。透過已經計算好的dp array從終點往前遞迴式的回推便能夠找到經過的路徑為何。 code如下: ```c= #include <stdio.h> #include <stdlib.h> #define MAX_PATH_LENGTH 6 #define PRINTDPMODE 0 typedef struct _position{ int x; int y; }position; //global variable int path_index = 0; position path[MAX_PATH_LENGTH]; int max(int a, int b){ return a > b ? a : b; } void freeMemory(int **array, int m){ for(int i = 0; i < m; i++){ free(array[i]); } free(array); } int **constructGrid(int m, int n){ int **grid = malloc(sizeof(int *) * m); for(int i = 0; i < m; i++){ grid[i] = malloc(sizeof(int) * n); } return grid; } int **initArray(int **array, int row, int col){ for(int i = 0; i < row; i++){ for(int j = 0; j < col; j++){ array[i][j] = 0; } } return array; } void print2DArray(int **array, int row, int col){ for(int i = 0; i < row; i++){ for(int j = 0; j < col; j++){ printf("%3d ", array[i][j]); } printf("\n"); } printf("\n"); } void initPath(position *path){ for(int i = 0; i < MAX_PATH_LENGTH; i++){ path[i].x = 0; path[i].y = 0; } } void printPath(position *path){ for(int i = 0; i < path_index; i++){ printf("(%d,%d) -> ", (path[i]).x, (path[i]).y); } printf("\n"); } void append(position *path, int i, int j){ (path[path_index]).x = i; (path[path_index]).y = j; path_index++; } //back trace the path recursively by dp array void getPath(int **dp, int i, int j, position *path){ //Base Case if(i==0 && j==0){ return append(path, i, j); } //can only come from left else if(i==0) getPath(dp, i, j-1, path); //can only come from top else if(j==0) getPath(dp, i-1, j, path); else{ if(dp[i-1][j] > dp[i][j-1]){ getPath(dp, i - 1, j, path); } else{ getPath(dp, i, j - 1, path); } } return append(path, i, j);; } int uniquePathsMaxProfit(int** grid, int m, int n){ //allocate 2D array int **dp = malloc(sizeof(int *) * m); for(int i = 0; i < m; i++){ dp[i] = malloc(sizeof(int) * n); } dp = initArray(dp, m, n); dp[0][0] = 0; //Inductive for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ //skip the base case if(i==0 && j==0) continue; //edge case if i > 0, j = 0, only top block can go to (i,j) else if(i > 0 && j==0) dp[i][j] = grid[i][j] + dp[i-1][j]; //edge case if i = 0, j > 0, only left block can go to (i,j) else if(i==0 && j > 0) dp[i][j] = grid[i][j] + dp[i][j-1]; //F(i,j) = grid[i][j] + max(F(i-1,j) , F(i,j-1)) else dp[i][j] = grid[i][j] + max(dp[i-1][j] , dp[i][j-1]); if(PRINTDPMODE){ print2DArray(dp, m, n); } } } int result = dp[m-1][n-1]; //find the path path_index = 0; initPath(path); getPath(dp, m-1, n-1, path); printPath(path); freeMemory(dp,m); return result; } int main(){ int **grid = constructGrid(3,4); //set the profit grid[0][0] = 0, grid[0][1] = 2, grid[0][2] = 2, grid[0][3] = 1; grid[1][0] = 3, grid[1][1] = 1, grid[1][2] = 1, grid[1][3] = 1; grid[2][0] = 4, grid[2][1] = 4, grid[2][2] = 2, grid[2][3] = 0; printf("max profit = %d\n", uniquePathsMaxProfit(grid, 3, 4)); //reset the grid grid[0][0] = 0, grid[0][1] = 2, grid[0][2] = 2, grid[0][3] = 50; grid[1][0] = 3, grid[1][1] = 1, grid[1][2] = 1, grid[1][3] = 100; grid[2][0] = 4, grid[2][1] = 4, grid[2][2] = 2, grid[2][3] = 0; printf("max profit = %d\n", uniquePathsMaxProfit(grid, 3, 4)); freeMemory(grid,3); return 0; } ``` ## Paint Fence(Combinatorial Problem) There is a fence with n posts, each post can be painted with either green or blue colors. You have to paint all the posts such that no more than two adjacent fence posts have the same color. Return the total number of ways you can paint the fence. 有一個以$n$個柵欄柱圍成的柵欄,要用綠色或是藍色來幫這$n$個柱子上色,限制為任一個柱子的左邊與又不能同色。問有幾種上色方法? ![](https://i.imgur.com/VsGMRn0.png) ![](https://i.imgur.com/t1LLPk0.png) * 1. 定義藍色為0,綠色為1 * 2. 定義$F(i,j)$為塗$i$個柱子所需要的方法數,並且第$i$個柵欄柱為$j$這個顏色 * 3. $F(i,j) = F(i-1,1-j) + F(i-2,1-j)$ * 4. 所求的答案為$F(n,0) + F(n,1)$ code如下: ```c= #include <stdio.h> #include <stdlib.h> void freeMemory(int **array, int n){ for(int i = 0; i <= n; i++){ free(array[i]); } free(array); } int numWays(int n){ int **dp = malloc(sizeof(int *) * (n + 1)); for(int i = 0; i <=n; i++){ //store two color dp[i] = malloc(sizeof(int) * 2); } //define green: 1, blue: 0 //Base Case dp[1][0] = 1; //0 dp[1][1] = 1; //1 dp[2][0] = 2; //00 10 dp[2][1] = 2; //01 11 for(int i = 3; i <= n; i++){ for(int j = 0; j <= 1; j++){ dp[i][j] = dp[i - 1][1 - j] + dp[i - 2][1 - j]; } } int result = dp[n][0] + dp[n][1]; freeMemory(dp,n); return result; } int main(){ printf("%d\n", numWays(3)); printf("%d\n", numWays(4)); printf("%d\n", numWays(5)); return 0; } ```

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully