Update knapsack_problem and intro_to_dp

Fix avl_tree
This commit is contained in:
krahets
2023-07-09 02:39:58 +08:00
parent cbfb9e59ad
commit cddddb8b8b
17 changed files with 61 additions and 48 deletions

View File

@@ -122,9 +122,9 @@ $$
也就是说,在爬楼梯问题中,**各个子问题之间不是相互独立的,原问题的解可以由子问题的解构成**。
我们可以基于此递推公式写出暴力搜索代码:以 $dp[n]$ 为起始点,**从顶至底地将一个较大问题拆解为两个较小问题的和**,直至到达最小子问题 $dp[1]$ 和 $dp[2]$ 时返回。其中,最小子问题的解是已知的,爬到第 $1$ , $2$ 阶分别有 $1$ , $2$ 种方案。
我们可以基于此递推公式写出暴力搜索代码:以 $dp[n]$ 为起始点,**从顶至底地将一个较大问题拆解为两个较小问题的和**,直至到达最小子问题 $dp[1]$ 和 $dp[2]$ 时返回。其中,最小子问题的解 $dp[1] = 1$ , $dp[2] = 2$ 是已知的,代表爬到第 $1$ , $2$ 阶分别有 $1$ , $2$ 种方案。
观察以下代码,它与回溯解法都属于深度优先搜索,但比回溯算法更加简洁。
观察以下代码,它和标准回溯代码都属于深度优先搜索,但更加简洁。
=== "Java"
@@ -214,7 +214,7 @@ $$
[class]{}-[func]{climbingStairsDFS}
```
下图展示了该方法形成的递归树。对于问题 $dp[n]$ ,递归树的深度为 $n$ ,时间复杂度为 $O(2^n)$ 。指数阶的运行时间增长地非常快,如果我们输入一个比较大的 $n$ ,则会陷入漫长的等待之中。
下图展示了暴力搜索形成的递归树。对于问题 $dp[n]$ 递归树的深度为 $n$ ,时间复杂度为 $O(2^n)$ 。指数阶的运行时间增长地非常快,如果我们输入一个比较大的 $n$ ,则会陷入漫长的等待之中。
![爬楼梯对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png)
@@ -393,15 +393,15 @@ $$
[class]{}-[func]{climbingStairsDP}
```
与回溯算法一样,动态规划也使用“状态”概念来表示问题求解的某个特定阶段,每个状态都对应一个子问题以及相应的局部最优解。例如对于爬楼梯问题,状态定义为当前所在楼梯阶数。**动态规划的常用术语包括**
与回溯算法一样,动态规划也使用“状态”概念来表示问题求解的某个特定阶段,每个状态都对应一个子问题以及相应的局部最优解。例如对于爬楼梯问题,状态定义为当前所在楼梯阶数 $i$ 。**动态规划的常用术语包括**
- 将 $dp$ 数组称为「状态列表」,$dp[i]$ 代表第 $i$ 个状态的解;
- 将最简单子问题对应的状态(即第 $1$ , $2$ 阶楼梯)称为「初始状态」;
- 将数组 `dp` 称为「$dp$ 表」,$dp[i]$ 表示状态 $i$ 对应子问题的解;
- 将最子问题对应的状态(即第 $1$ , $2$ 阶楼梯)称为「初始状态」;
- 将递推公式 $dp[i] = dp[i-1] + dp[i-2]$ 称为「状态转移方程」;
![爬楼梯的动态规划过程](intro_to_dynamic_programming.assets/climbing_stairs_dp.png)
细心的你可能发现,**由于 $dp[i]$ 只与 $dp[i-1]$ 和 $dp[i-2]$ 有关,因此我们无需使用一个数组 `dp` 来存储所有状态**,而只需两个变量滚动前进即可。如以下代码所示,由于省去了数组 `dp` 占用的空间,因此空间复杂度从 $O(n)$ 降低至 $O(1)$ 。
细心的你可能发现,**由于 $dp[i]$ 只与 $dp[i-1]$ 和 $dp[i-2]$ 有关,因此我们无需使用一个数组 `dp` 来存储所有子问题的解**,而只需两个变量滚动前进即可。如以下代码所示,由于省去了数组 `dp` 占用的空间,因此空间复杂度从 $O(n)$ 降低至 $O(1)$ 。
=== "Java"