This commit is contained in:
krahets
2025-12-31 19:37:45 +08:00
parent 29ec0c699d
commit 3c9d5689c4
279 changed files with 40895 additions and 16087 deletions

View File

@@ -160,12 +160,6 @@ $$
[class]{}-[func]{min_cost_climbing_stairs_dp}
```
=== "Zig"
```zig title="min_cost_climbing_stairs_dp.zig"
[class]{}-[func]{minCostClimbingStairsDP}
```
下の図は上記コードの動的プログラミングプロセスを示しています。
![階段登りの最小コストの動的プログラミングプロセス](dp_problem_features.assets/min_cost_cs_dp.png){ class="animation-figure" }
@@ -284,12 +278,6 @@ $$
[class]{}-[func]{min_cost_climbing_stairs_dp_comp}
```
=== "Zig"
```zig title="min_cost_climbing_stairs_dp.zig"
[class]{}-[func]{minCostClimbingStairsDPComp}
```
## 14.2.2   無記憶性
無記憶性は動的プログラミングが問題解決に効果的であることを可能にする重要な特徴の1つです。その定義は**特定の状態が与えられたとき、その将来の発展は現在の状態のみに関連し、過去に経験したすべての状態とは無関係である**。
@@ -459,12 +447,6 @@ $$
[class]{}-[func]{climbing_stairs_constraint_dp}
```
=== "Zig"
```zig title="climbing_stairs_constraint_dp.zig"
[class]{}-[func]{climbingStairsConstraintDP}
```
上記のケースでは、前の状態のみを考慮すればよいため、状態定義を拡張することで依然として無記憶性を満たすことができます。しかし、一部の問題では非常に深刻な「状態効果」があります。
!!! question "障害物生成付き階段登り"

View File

@@ -232,12 +232,6 @@ $$
[class]{}-[func]{min_path_sum_dfs}
```
=== "Zig"
```zig title="min_path_sum.zig"
[class]{}-[func]{minPathSumDFS}
```
下の図は $dp[2, 1]$ を根とする再帰木を示しており、いくつかの重複する部分問題を含み、その数はグリッド `grid` のサイズが増加すると急激に増加します。
本質的に、重複する部分問題の理由は:**左上隅から特定のセルに到達する複数のパスが存在する**ことです。
@@ -368,12 +362,6 @@ $$
[class]{}-[func]{min_path_sum_dfs_mem}
```
=== "Zig"
```zig title="min_path_sum.zig"
[class]{}-[func]{minPathSumDFSMem}
```
下の図に示すように、メモ化を導入した後、すべての部分問題の解は一度だけ計算される必要があるため、時間計算量は状態の総数、つまりグリッドサイズ $O(nm)$ に依存します。
![メモ化探索の再帰木](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png){ class="animation-figure" }
@@ -520,12 +508,6 @@ $$
[class]{}-[func]{min_path_sum_dp}
```
=== "Zig"
```zig title="min_path_sum.zig"
[class]{}-[func]{minPathSumDP}
```
下の図は最小経路和の状態遷移プロセスを示し、グリッド全体を走査するため、**時間計算量は $O(nm)$** です。
配列 `dp` のサイズは $n \times m$ であるため、**空間計算量は $O(nm)$** です。
@@ -687,9 +669,3 @@ $$
```ruby title="min_path_sum.rb"
[class]{}-[func]{min_path_sum_dp_comp}
```
=== "Zig"
```zig title="min_path_sum.zig"
[class]{}-[func]{minPathSumDPComp}
```

View File

@@ -221,12 +221,6 @@ $$
[class]{}-[func]{edit_distance_dp}
```
=== "Zig"
```zig title="edit_distance.zig"
[class]{}-[func]{editDistanceDP}
```
下の図に示すように、編集距離問題の状態遷移プロセスはナップサック問題と非常に似ており、二次元グリッドを埋めることと見なすことができます。
=== "<1>"
@@ -408,9 +402,3 @@ $dp[i, j]$ は上の $dp[i-1, j]$、左の $dp[i, j-1]$、左上の $dp[i-1, j-1
```ruby title="edit_distance.rb"
[class]{}-[func]{edit_distance_dp_comp}
```
=== "Zig"
```zig title="edit_distance.zig"
[class]{}-[func]{editDistanceDPComp}
```

View File

@@ -185,14 +185,6 @@ comments: true
[class]{}-[func]{climbing_stairs_backtrack}
```
=== "Zig"
```zig title="climbing_stairs_backtrack.zig"
[class]{}-[func]{backtrack}
[class]{}-[func]{climbingStairsBacktrack}
```
## 14.1.1 &nbsp; 方法1力任せ探索
バックトラッキングアルゴリズムは問題を明示的に部分問題に分解しません。代わりに、問題を一連の決定ステップとして扱い、試行と枝刈りを通じてすべての可能性を探索します。
@@ -356,14 +348,6 @@ $$
[class]{}-[func]{climbing_stairs_dfs}
```
=== "Zig"
```zig title="climbing_stairs_dfs.zig"
[class]{}-[func]{dfs}
[class]{}-[func]{climbingStairsDFS}
```
下の図は力任せ探索によって形成される再帰木を示しています。問題 $dp[n]$ について、その再帰木の深さは $n$ で、時間計算量は $O(2^n)$ です。この指数的増加により、$n$ が大きいとプログラムの実行がはるかに遅くなり、長い待機時間が生じます。
![階段登りの再帰木](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png){ class="animation-figure" }
@@ -540,14 +524,6 @@ $$
[class]{}-[func]{climbing_stairs_dfs_mem}
```
=== "Zig"
```zig title="climbing_stairs_dfs_mem.zig"
[class]{}-[func]{dfs}
[class]{}-[func]{climbingStairsDFSMem}
```
下の図を観察すると、**メモ化後、すべての重複する部分問題は一度だけ計算される必要があり、時間計算量を $O(n)$ に最適化**します。これは大幅な改善です。
![メモ化探索による再帰木](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png){ class="animation-figure" }
@@ -679,12 +655,6 @@ $$
[class]{}-[func]{climbing_stairs_dp}
```
=== "Zig"
```zig title="climbing_stairs_dp.zig"
[class]{}-[func]{climbingStairsDP}
```
下の図は上記コードの実行プロセスをシミュレートしています。
![階段登りの動的プログラミングプロセス](intro_to_dynamic_programming.assets/climbing_stairs_dp.png){ class="animation-figure" }
@@ -810,12 +780,6 @@ $$
[class]{}-[func]{climbing_stairs_dp_comp}
```
=== "Zig"
```zig title="climbing_stairs_dp.zig"
[class]{}-[func]{climbingStairsDPComp}
```
上記のコードを観察すると、配列 `dp` が占有していた空間が削除されるため、空間計算量は $O(n)$ から $O(1)$ に削減されます。
多くの動的プログラミング問題では、現在の状態は限られた数の前の状態のみに依存するため、必要な状態のみを保持し、「次元削減」によってメモリ空間を節約できます。**この空間最適化技術は「ローリング変数」または「ローリング配列」として知られています**。

View File

@@ -182,12 +182,6 @@ $$
[class]{}-[func]{knapsack_dfs}
```
=== "Zig"
```zig title="knapsack.zig"
[class]{}-[func]{knapsackDFS}
```
下の図に示すように、各アイテムは選択しないと選択するという2つの探索分岐を生成するため、時間計算量は $O(2^n)$ です。
再帰木を観察すると、$dp[1, 10]$ などの重複する部分問題があることが容易にわかります。アイテムが多く、ナップサック容量が大きい場合、特に同じ重量のアイテムが多い場合、重複する部分問題の数は大幅に増加します。
@@ -318,12 +312,6 @@ $$
[class]{}-[func]{knapsack_dfs_mem}
```
=== "Zig"
```zig title="knapsack.zig"
[class]{}-[func]{knapsackDFSMem}
```
下の図はメモ化探索で枝刈りされる探索分岐を示しています。
![0-1ナップサック問題のメモ化探索再帰木](knapsack_problem.assets/knapsack_dfs_mem.png){ class="animation-figure" }
@@ -462,12 +450,6 @@ $$
[class]{}-[func]{knapsack_dp}
```
=== "Zig"
```zig title="knapsack.zig"
[class]{}-[func]{knapsackDP}
```
下の図に示すように、時間計算量と空間計算量の両方が配列 `dp` のサイズ、つまり $O(n \times cap)$ によって決定されます。
=== "<1>"
@@ -671,9 +653,3 @@ $$
```ruby title="knapsack.rb"
[class]{}-[func]{knapsack_dp_comp}
```
=== "Zig"
```zig title="knapsack.zig"
[class]{}-[func]{knapsackDPComp}
```

View File

@@ -166,12 +166,6 @@ $$
[class]{}-[func]{unbounded_knapsack_dp}
```
=== "Zig"
```zig title="unbounded_knapsack.zig"
[class]{}-[func]{unboundedKnapsackDP}
```
### 3. &nbsp; 空間最適化
現在の状態は左と上の状態から来るため、**空間最適化解法は $dp$ テーブルの各行に対して前方走査を実行する必要があります**。
@@ -329,12 +323,6 @@ $$
[class]{}-[func]{unbounded_knapsack_dp_comp}
```
=== "Zig"
```zig title="unbounded_knapsack.zig"
[class]{}-[func]{unboundedKnapsackDPComp}
```
## 14.5.2 &nbsp; コイン交換問題
ナップサック問題は動的プログラミング問題の大きなクラスの代表であり、コイン交換問題など多くの変種があります。
@@ -526,12 +514,6 @@ $$
[class]{}-[func]{coin_change_dp}
```
=== "Zig"
```zig title="coin_change.zig"
[class]{}-[func]{coinChangeDP}
```
下の図はコイン交換問題の動的プログラミングプロセスを示しており、無制限ナップサック問題と非常に似ています。
=== "<1>"
@@ -721,12 +703,6 @@ $$
[class]{}-[func]{coin_change_dp_comp}
```
=== "Zig"
```zig title="coin_change.zig"
[class]{}-[func]{coinChangeDPComp}
```
## 14.5.3 &nbsp; コイン交換問題II
!!! question
@@ -890,12 +866,6 @@ $$
[class]{}-[func]{coin_change_ii_dp}
```
=== "Zig"
```zig title="coin_change_ii.zig"
[class]{}-[func]{coinChangeIIDP}
```
### 3. &nbsp; 空間最適化
空間最適化アプローチは同じで、コインの次元を削除するだけです:
@@ -1031,9 +1001,3 @@ $$
```ruby title="coin_change_ii.rb"
[class]{}-[func]{coin_change_ii_dp_comp}
```
=== "Zig"
```zig title="coin_change_ii.zig"
[class]{}-[func]{coinChangeIIDPComp}
```