This commit is contained in:
krahets
2026-04-02 03:08:50 +08:00
parent 09a136c9fa
commit aaf9f58eb3
157 changed files with 3002 additions and 2994 deletions
@@ -6,7 +6,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code">
<meta name="description" content="Data structures and algorithms tutorial with animated illustrations and ready-to-run code">
<meta name="author" content="krahets">
@@ -576,7 +576,7 @@
<span class="md-ellipsis">
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
@@ -598,7 +598,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
</label>
@@ -1183,7 +1183,7 @@
<span class="md-ellipsis">
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
@@ -1205,7 +1205,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
</label>
@@ -1311,7 +1311,7 @@
<span class="md-ellipsis">
4.4 Memory and Cache *
4.4 Random-Access Memory and Cache *
@@ -1402,7 +1402,7 @@
<span class="md-ellipsis">
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
@@ -1424,7 +1424,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
</label>
@@ -1502,7 +1502,7 @@
<span class="md-ellipsis">
5.3 Double-Ended Queue
5.3 Deque
@@ -1593,7 +1593,7 @@
<span class="md-ellipsis">
Chapter 6. Hashing
Chapter 6. Hash Table
@@ -1615,7 +1615,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 6. Hashing
Chapter 6. Hash Table
</label>
@@ -1888,7 +1888,7 @@
<span class="md-ellipsis">
7.3 Array Representation of Tree
7.3 Array Representation of Binary Trees
@@ -2107,7 +2107,7 @@
<span class="md-ellipsis">
8.2 Building a Heap
8.2 Heap Construction Operation
@@ -2135,7 +2135,7 @@
<span class="md-ellipsis">
8.3 Top-K Problem
8.3 Top-k Problem
@@ -2493,7 +2493,7 @@
<span class="md-ellipsis">
10.2 Binary Search Insertion
10.2 Binary Search Insertion Point
@@ -2521,7 +2521,7 @@
<span class="md-ellipsis">
10.3 Binary Search Edge Cases
10.3 Binary Search Boundaries
@@ -2577,7 +2577,7 @@
<span class="md-ellipsis">
10.5 Search Algorithms Revisited
10.5 Searching Algorithms Revisited
@@ -2726,7 +2726,7 @@
<span class="md-ellipsis">
11.1 Sorting Algorithms
11.1 Sorting Algorithm
@@ -3199,7 +3199,7 @@
<span class="md-ellipsis">
12.4 Hanoi Tower Problem
12.4 Hanota Problem
@@ -4183,7 +4183,7 @@
<span class="md-ellipsis">
16.3 Terminology Table
16.3 Glossary
@@ -4368,7 +4368,7 @@
<p>We make a slight modification to the stair climbing problem to make it more suitable for demonstrating the concept of optimal substructure.</p>
<div class="admonition question">
<p class="admonition-title">Climbing stairs with minimum cost</p>
<p>Given a staircase, where you can climb <span class="arithmatex">\(1\)</span> or <span class="arithmatex">\(2\)</span> steps at a time, and each step has a non-negative integer representing the cost you need to pay at that step. Given a non-negative integer array <span class="arithmatex">\(cost\)</span>, where <span class="arithmatex">\(cost[i]\)</span> represents the cost at the <span class="arithmatex">\(i\)</span>-th step, and <span class="arithmatex">\(cost[0]\)</span> is the ground (starting point). What is the minimum cost required to reach the top?</p>
<p>Given a staircase, you can climb <span class="arithmatex">\(1\)</span> or <span class="arithmatex">\(2\)</span> steps at a time, and each step is labeled with a non-negative integer representing the cost of stepping on it. Given a non-negative integer array <span class="arithmatex">\(cost\)</span>, where <span class="arithmatex">\(cost[i]\)</span> represents the cost of the <span class="arithmatex">\(i\)</span>-th step and <span class="arithmatex">\(cost[0]\)</span> is the ground (starting point), what is the minimum cost required to reach the top?</p>
</div>
<p>As shown in Figure 14-6, if the costs of the <span class="arithmatex">\(1\)</span>st, <span class="arithmatex">\(2\)</span>nd, and <span class="arithmatex">\(3\)</span>rd steps are <span class="arithmatex">\(1\)</span>, <span class="arithmatex">\(10\)</span>, and <span class="arithmatex">\(1\)</span> respectively, then climbing from the ground to the <span class="arithmatex">\(3\)</span>rd step requires a minimum cost of <span class="arithmatex">\(2\)</span>.</p>
<p><img alt="Minimum cost to climb to the 3rd step" class="animation-figure" src="../dp_problem_features.assets/min_cost_cs_example.png" /></p>
@@ -4879,7 +4879,7 @@ dp[i] = \min(dp[i-1], dp[i-2]) + cost[i]
<p class="admonition-title">Climbing stairs with constraint</p>
<p>Given a staircase with <span class="arithmatex">\(n\)</span> steps, where you can climb <span class="arithmatex">\(1\)</span> or <span class="arithmatex">\(2\)</span> steps at a time, <strong>but you cannot jump <span class="arithmatex">\(1\)</span> step in two consecutive rounds</strong>. How many ways are there to climb to the top?</p>
</div>
<p>As shown in Figure 14-8, there are only <span class="arithmatex">\(2\)</span> feasible ways to climb to the <span class="arithmatex">\(3\)</span>rd step. The way of jumping <span class="arithmatex">\(1\)</span> step three consecutive times does not satisfy the constraint and is therefore discarded.</p>
<p>As shown in Figure 14-8, there are only <span class="arithmatex">\(2\)</span> feasible ways to climb to the <span class="arithmatex">\(3\)</span>rd step. The path with three consecutive <span class="arithmatex">\(1\)</span>-step jumps does not satisfy the constraint and is therefore discarded.</p>
<p><img alt="Number of ways to climb to the 3rd step with constraint" class="animation-figure" src="../dp_problem_features.assets/climbing_stairs_constraint_example.png" /></p>
<p align="center"> Figure 14-8 &nbsp; Number of ways to climb to the 3rd step with constraint </p>
@@ -4887,8 +4887,8 @@ dp[i] = \min(dp[i-1], dp[i-2]) + cost[i]
<p>It is not difficult to see that this problem no longer satisfies no aftereffects, and the state transition equation <span class="arithmatex">\(dp[i] = dp[i-1] + dp[i-2]\)</span> also fails, because <span class="arithmatex">\(dp[i-1]\)</span> represents jumping <span class="arithmatex">\(1\)</span> step in this round, but it includes many solutions where "the previous round was a jump of <span class="arithmatex">\(1\)</span> step", which cannot be directly counted in <span class="arithmatex">\(dp[i]\)</span> to satisfy the constraint.</p>
<p>For this reason, we need to expand the state definition: <strong>state <span class="arithmatex">\([i, j]\)</span> represents being on the <span class="arithmatex">\(i\)</span>-th step with the previous round having jumped <span class="arithmatex">\(j\)</span> steps</strong>, where <span class="arithmatex">\(j \in \{1, 2\}\)</span>. This state definition effectively distinguishes whether the previous round was a jump of <span class="arithmatex">\(1\)</span> step or <span class="arithmatex">\(2\)</span> steps, allowing us to determine where the current state came from.</p>
<ul>
<li>When the previous round jumped <span class="arithmatex">\(1\)</span> step, the round before that could only choose to jump <span class="arithmatex">\(2\)</span> steps, i.e., <span class="arithmatex">\(dp[i, 1]\)</span> can only be transferred from <span class="arithmatex">\(dp[i-1, 2]\)</span>.</li>
<li>When the previous round jumped <span class="arithmatex">\(2\)</span> steps, the round before that could choose to jump <span class="arithmatex">\(1\)</span> step or <span class="arithmatex">\(2\)</span> steps, i.e., <span class="arithmatex">\(dp[i, 2]\)</span> can be transferred from <span class="arithmatex">\(dp[i-2, 1]\)</span> or <span class="arithmatex">\(dp[i-2, 2]\)</span>.</li>
<li>When the previous round jumped <span class="arithmatex">\(1\)</span> step, the round before that could only choose to jump <span class="arithmatex">\(2\)</span> steps, i.e., <span class="arithmatex">\(dp[i, 1]\)</span> can only transition from <span class="arithmatex">\(dp[i-1, 2]\)</span>.</li>
<li>When the previous round jumped <span class="arithmatex">\(2\)</span> steps, the round before that could choose to jump <span class="arithmatex">\(1\)</span> step or <span class="arithmatex">\(2\)</span> steps, i.e., <span class="arithmatex">\(dp[i, 2]\)</span> can transition from <span class="arithmatex">\(dp[i-2, 1]\)</span> or <span class="arithmatex">\(dp[i-2, 2]\)</span>.</li>
</ul>
<p>As shown in Figure 14-9, under this definition, <span class="arithmatex">\(dp[i, j]\)</span> represents the number of ways for state <span class="arithmatex">\([i, j]\)</span>. The state transition equation is then:</p>
<div class="arithmatex">\[
@@ -5196,10 +5196,10 @@ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2]
<p>In the above case, since we only need to consider one more preceding state, we can still make the problem satisfy no aftereffects by expanding the state definition. However, some problems have very severe "aftereffects".</p>
<div class="admonition question">
<p class="admonition-title">Climbing stairs with obstacle generation</p>
<p>Given a staircase with <span class="arithmatex">\(n\)</span> steps, where you can climb <span class="arithmatex">\(1\)</span> or <span class="arithmatex">\(2\)</span> steps at a time. <strong>It is stipulated that when climbing to the <span class="arithmatex">\(i\)</span>-th step, the system will automatically place an obstacle on the <span class="arithmatex">\(2i\)</span>-th step, and thereafter no round is allowed to jump to the <span class="arithmatex">\(2i\)</span>-th step</strong>. For example, if the first two rounds jump to the <span class="arithmatex">\(2\)</span>nd and <span class="arithmatex">\(3\)</span>rd steps, then afterwards you cannot jump to the <span class="arithmatex">\(4\)</span>th and <span class="arithmatex">\(6\)</span>th steps. How many ways are there to climb to the top?</p>
<p>Given a staircase with <span class="arithmatex">\(n\)</span> steps, where you can climb <span class="arithmatex">\(1\)</span> or <span class="arithmatex">\(2\)</span> steps at a time. <strong>Whenever you reach the <span class="arithmatex">\(i\)</span>-th step, the system automatically places an obstacle on the <span class="arithmatex">\(2i\)</span>-th step, and no subsequent round is allowed to jump to the <span class="arithmatex">\(2i\)</span>-th step</strong>. For example, if the first two rounds jump to the <span class="arithmatex">\(2\)</span>nd and <span class="arithmatex">\(3\)</span>rd steps, then afterwards you cannot jump to the <span class="arithmatex">\(4\)</span>th and <span class="arithmatex">\(6\)</span>th steps. How many ways are there to climb to the top?</p>
</div>
<p>In this problem, the next jump depends on all past states, because each jump places obstacles on higher steps, affecting future jumps. For such problems, dynamic programming is often difficult to solve.</p>
<p>In fact, many complex combinatorial optimization problems (such as the traveling salesman problem) do not satisfy no aftereffects. For such problems, we usually choose to use other methods, such as heuristic search, genetic algorithms, reinforcement learning, etc., to obtain usable local optimal solutions within a limited time.</p>
<p>In fact, many complex combinatorial optimization problems (such as the traveling salesman problem) do not satisfy no aftereffects. For such problems, we usually use other methods, such as heuristic search, genetic algorithms, and reinforcement learning, to obtain usable locally optimal solutions within a limited time.</p>
<!-- Source file information -->
@@ -6,7 +6,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code">
<meta name="description" content="Data structures and algorithms tutorial with animated illustrations and ready-to-run code">
<meta name="author" content="krahets">
@@ -576,7 +576,7 @@
<span class="md-ellipsis">
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
@@ -598,7 +598,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
</label>
@@ -1183,7 +1183,7 @@
<span class="md-ellipsis">
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
@@ -1205,7 +1205,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
</label>
@@ -1311,7 +1311,7 @@
<span class="md-ellipsis">
4.4 Memory and Cache *
4.4 Random-Access Memory and Cache *
@@ -1402,7 +1402,7 @@
<span class="md-ellipsis">
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
@@ -1424,7 +1424,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
</label>
@@ -1502,7 +1502,7 @@
<span class="md-ellipsis">
5.3 Double-Ended Queue
5.3 Deque
@@ -1593,7 +1593,7 @@
<span class="md-ellipsis">
Chapter 6. Hashing
Chapter 6. Hash Table
@@ -1615,7 +1615,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 6. Hashing
Chapter 6. Hash Table
</label>
@@ -1888,7 +1888,7 @@
<span class="md-ellipsis">
7.3 Array Representation of Tree
7.3 Array Representation of Binary Trees
@@ -2107,7 +2107,7 @@
<span class="md-ellipsis">
8.2 Building a Heap
8.2 Heap Construction Operation
@@ -2135,7 +2135,7 @@
<span class="md-ellipsis">
8.3 Top-K Problem
8.3 Top-k Problem
@@ -2493,7 +2493,7 @@
<span class="md-ellipsis">
10.2 Binary Search Insertion
10.2 Binary Search Insertion Point
@@ -2521,7 +2521,7 @@
<span class="md-ellipsis">
10.3 Binary Search Edge Cases
10.3 Binary Search Boundaries
@@ -2577,7 +2577,7 @@
<span class="md-ellipsis">
10.5 Search Algorithms Revisited
10.5 Searching Algorithms Revisited
@@ -2726,7 +2726,7 @@
<span class="md-ellipsis">
11.1 Sorting Algorithms
11.1 Sorting Algorithm
@@ -3199,7 +3199,7 @@
<span class="md-ellipsis">
12.4 Hanoi Tower Problem
12.4 Hanota Problem
@@ -3673,10 +3673,10 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#1431-problem-determination" class="md-nav__link">
<a href="#1431-problem-identification" class="md-nav__link">
<span class="md-ellipsis">
14.3.1 &nbsp; Problem Determination
14.3.1 &nbsp; Problem Identification
</span>
</a>
@@ -4233,7 +4233,7 @@
<span class="md-ellipsis">
16.3 Terminology Table
16.3 Glossary
@@ -4348,10 +4348,10 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#1431-problem-determination" class="md-nav__link">
<a href="#1431-problem-identification" class="md-nav__link">
<span class="md-ellipsis">
14.3.1 &nbsp; Problem Determination
14.3.1 &nbsp; Problem Identification
</span>
</a>
@@ -4462,27 +4462,27 @@
<li>How to determine whether a problem is a dynamic programming problem?</li>
<li>What is the complete process for solving a dynamic programming problem, and where should we start?</li>
</ol>
<h2 id="1431-problem-determination">14.3.1 &nbsp; Problem Determination<a class="headerlink" href="#1431-problem-determination" title="Permanent link">&para;</a></h2>
<h2 id="1431-problem-identification">14.3.1 &nbsp; Problem Identification<a class="headerlink" href="#1431-problem-identification" title="Permanent link">&para;</a></h2>
<p>Generally speaking, if a problem contains overlapping subproblems, optimal substructure, and satisfies no aftereffects, then it is usually suitable for solving with dynamic programming. However, it is difficult to directly extract these characteristics from the problem description. Therefore, we usually relax the conditions and <strong>first observe whether the problem is suitable for solving with backtracking (exhaustive search)</strong>.</p>
<p><strong>Problems suitable for solving with backtracking usually satisfy the "decision tree model"</strong>, which means the problem can be described using a tree structure, where each node represents a decision and each path represents a sequence of decisions.</p>
<p>In other words, if a problem contains an explicit concept of decisions, and the solution is generated through a series of decisions, then it satisfies the decision tree model and can usually be solved using backtracking.</p>
<p>On this basis, dynamic programming problems also have some "bonus points" for determination.</p>
<p>On this basis, dynamic programming problems also have some positive indicators.</p>
<ul>
<li>The problem contains descriptions such as maximum (minimum) or most (least), indicating optimization.</li>
<li>The problem's state can be represented using a list, multi-dimensional matrix, or tree, and a state has a recurrence relation with its surrounding states.</li>
</ul>
<p>Correspondingly, there are also some "penalty points".</p>
<p>Correspondingly, there are also some negative indicators.</p>
<ul>
<li>The goal of the problem is to find all possible solutions, rather than finding the optimal solution.</li>
<li>The problem description has obvious permutation and combination characteristics, requiring the return of specific multiple solutions.</li>
</ul>
<p>If a problem satisfies the decision tree model and has relatively obvious "bonus points", we can assume it is a dynamic programming problem and verify it during the solving process.</p>
<p>If a problem satisfies the decision tree model and has relatively obvious positive indicators, we can assume it is a dynamic programming problem and verify that assumption during the solving process.</p>
<h2 id="1432-problem-solving-steps">14.3.2 &nbsp; Problem-Solving Steps<a class="headerlink" href="#1432-problem-solving-steps" title="Permanent link">&para;</a></h2>
<p>The problem-solving process for dynamic programming varies depending on the nature and difficulty of the problem, but generally follows these steps: describe decisions, define states, establish the <span class="arithmatex">\(dp\)</span> table, derive state transition equations, determine boundary conditions, etc.</p>
<p>To illustrate the problem-solving steps more vividly, we use a classic problem "minimum path sum" as an example.</p>
<div class="admonition question">
<p class="admonition-title">Question</p>
<p>Given an <span class="arithmatex">\(n \times m\)</span> two-dimensional grid <code>grid</code>, where each cell in the grid contains a non-negative integer representing the cost of that cell. A robot starts from the top-left cell and can only move down or right at each step until reaching the bottom-right cell. Return the minimum path sum from the top-left to the bottom-right.</p>
<p>Given an <span class="arithmatex">\(n \times m\)</span> two-dimensional grid <code>grid</code> in which each cell contains a non-negative integer representing its cost, a robot starts from the top-left cell and can only move down or right at each step until reaching the bottom-right cell. Return the minimum path sum from the top-left to the bottom-right.</p>
</div>
<p>Figure 14-10 shows an example where the minimum path sum for the given grid is <span class="arithmatex">\(13\)</span>.</p>
<p><img alt="Minimum path sum example data" class="animation-figure" src="../dp_solution_pipeline.assets/min_path_sum_example.png" /></p>
@@ -4501,7 +4501,7 @@
<p>Each state corresponds to a subproblem, and we define a <span class="arithmatex">\(dp\)</span> table to store the solutions to all subproblems. Each independent variable of the state is a dimension of the <span class="arithmatex">\(dp\)</span> table. Essentially, the <span class="arithmatex">\(dp\)</span> table is a mapping between states and solutions to subproblems.</p>
</div>
<p><strong>Step 2: Identify the optimal substructure, and then derive the state transition equation</strong></p>
<p>For state <span class="arithmatex">\([i, j]\)</span>, it can only be transferred from the cell above <span class="arithmatex">\([i-1, j]\)</span> or the cell to the left <span class="arithmatex">\([i, j-1]\)</span>. Therefore, the optimal substructure is: the minimum path sum to reach <span class="arithmatex">\([i, j]\)</span> is determined by the smaller of the minimum path sums of <span class="arithmatex">\([i, j-1]\)</span> and <span class="arithmatex">\([i-1, j]\)</span>.</p>
<p>For state <span class="arithmatex">\([i, j]\)</span>, it can only transition from the cell above <span class="arithmatex">\([i-1, j]\)</span> or the cell to the left <span class="arithmatex">\([i, j-1]\)</span>. Therefore, the optimal substructure is: the minimum path sum to reach <span class="arithmatex">\([i, j]\)</span> is determined by the smaller of the minimum path sums of <span class="arithmatex">\([i, j-1]\)</span> and <span class="arithmatex">\([i-1, j]\)</span>.</p>
<p>Based on the above analysis, the state transition equation shown in Figure 14-12 can be derived:</p>
<div class="arithmatex">\[
dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]
@@ -4516,18 +4516,18 @@ dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]
</div>
<p><strong>Step 3: Determine boundary conditions and state transition order</strong></p>
<p>In this problem, states in the first row can only come from the state to their left, and states in the first column can only come from the state above them. Therefore, the first row <span class="arithmatex">\(i = 0\)</span> and first column <span class="arithmatex">\(j = 0\)</span> are boundary conditions.</p>
<p>As shown in Figure 14-13, since each cell is transferred from the cell to its left and the cell above it, we use loops to traverse the matrix, with the outer loop traversing rows and the inner loop traversing columns.</p>
<p>As shown in Figure 14-13, since each cell transitions from the cell to its left and the cell above it, we use loops to traverse the matrix, with the outer loop traversing rows and the inner loop traversing columns.</p>
<p><img alt="Boundary conditions and state transition order" class="animation-figure" src="../dp_solution_pipeline.assets/min_path_sum_solution_initial_state.png" /></p>
<p align="center"> Figure 14-13 &nbsp; Boundary conditions and state transition order </p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Boundary conditions in dynamic programming are used to initialize the <span class="arithmatex">\(dp\)</span> table, and in search are used for pruning.</p>
<p>Boundary conditions in dynamic programming are used to initialize the <span class="arithmatex">\(dp\)</span> table, while in search they are used for pruning.</p>
<p>The core of state transition order is to ensure that when computing the solution to the current problem, all the smaller subproblems it depends on have already been computed correctly.</p>
</div>
<p>Based on the above analysis, we can directly write the dynamic programming code. However, subproblem decomposition is a top-down approach, so implementing in the order "brute force search <span class="arithmatex">\(\rightarrow\)</span> memoization <span class="arithmatex">\(\rightarrow\)</span> dynamic programming" is more aligned with thinking habits.</p>
<h3 id="1-method-1-brute-force-search">1. &nbsp; Method 1: Brute Force Search<a class="headerlink" href="#1-method-1-brute-force-search" title="Permanent link">&para;</a></h3>
<p>Starting from state <span class="arithmatex">\([i, j]\)</span>, continuously decompose into smaller states <span class="arithmatex">\([i-1, j]\)</span> and <span class="arithmatex">\([i, j-1]\)</span>. The recursive function includes the following elements.</p>
<p>Starting from state <span class="arithmatex">\([i, j]\)</span>, we continuously decompose it into smaller states <span class="arithmatex">\([i-1, j]\)</span> and <span class="arithmatex">\([i, j-1]\)</span>. The recursive function includes the following elements.</p>
<ul>
<li><strong>Recursive parameters</strong>: state <span class="arithmatex">\([i, j]\)</span>.</li>
<li><strong>Return value</strong>: minimum path sum from <span class="arithmatex">\([0, 0]\)</span> to <span class="arithmatex">\([i, j]\)</span>, which is <span class="arithmatex">\(dp[i, j]\)</span>.</li>
@@ -6,7 +6,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code">
<meta name="description" content="Data structures and algorithms tutorial with animated illustrations and ready-to-run code">
<meta name="author" content="krahets">
@@ -576,7 +576,7 @@
<span class="md-ellipsis">
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
@@ -598,7 +598,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
</label>
@@ -1183,7 +1183,7 @@
<span class="md-ellipsis">
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
@@ -1205,7 +1205,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
</label>
@@ -1311,7 +1311,7 @@
<span class="md-ellipsis">
4.4 Memory and Cache *
4.4 Random-Access Memory and Cache *
@@ -1402,7 +1402,7 @@
<span class="md-ellipsis">
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
@@ -1424,7 +1424,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
</label>
@@ -1502,7 +1502,7 @@
<span class="md-ellipsis">
5.3 Double-Ended Queue
5.3 Deque
@@ -1593,7 +1593,7 @@
<span class="md-ellipsis">
Chapter 6. Hashing
Chapter 6. Hash Table
@@ -1615,7 +1615,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 6. Hashing
Chapter 6. Hash Table
</label>
@@ -1888,7 +1888,7 @@
<span class="md-ellipsis">
7.3 Array Representation of Tree
7.3 Array Representation of Binary Trees
@@ -2107,7 +2107,7 @@
<span class="md-ellipsis">
8.2 Building a Heap
8.2 Heap Construction Operation
@@ -2135,7 +2135,7 @@
<span class="md-ellipsis">
8.3 Top-K Problem
8.3 Top-k Problem
@@ -2493,7 +2493,7 @@
<span class="md-ellipsis">
10.2 Binary Search Insertion
10.2 Binary Search Insertion Point
@@ -2521,7 +2521,7 @@
<span class="md-ellipsis">
10.3 Binary Search Edge Cases
10.3 Binary Search Boundaries
@@ -2577,7 +2577,7 @@
<span class="md-ellipsis">
10.5 Search Algorithms Revisited
10.5 Searching Algorithms Revisited
@@ -2726,7 +2726,7 @@
<span class="md-ellipsis">
11.1 Sorting Algorithms
11.1 Sorting Algorithm
@@ -3199,7 +3199,7 @@
<span class="md-ellipsis">
12.4 Hanoi Tower Problem
12.4 Hanota Problem
@@ -4194,7 +4194,7 @@
<span class="md-ellipsis">
16.3 Terminology Table
16.3 Glossary
@@ -4389,7 +4389,7 @@
<p><img alt="Example data for edit distance" class="animation-figure" src="../edit_distance_problem.assets/edit_distance_example.png" /></p>
<p align="center"> Figure 14-27 &nbsp; Example data for edit distance </p>
<p><strong>The edit distance problem can be naturally explained using the decision tree model</strong>. Strings correspond to tree nodes, and a round of decision (one edit operation) corresponds to an edge of the tree.</p>
<p><strong>The edit distance problem can be naturally explained using the decision tree model</strong>. Strings correspond to tree nodes, and each edit operation corresponds to an edge in the tree.</p>
<p>As shown in Figure 14-28, without restricting operations, each node can branch into many edges, with each edge corresponding to one operation, meaning there are many possible paths to transform <code>hello</code> into <code>algo</code>.</p>
<p>From the perspective of the decision tree, the goal of this problem is to find the shortest path between node <code>hello</code> and node <code>algo</code>.</p>
<p><img alt="Representing edit distance problem based on decision tree model" class="animation-figure" src="../edit_distance_problem.assets/edit_distance_decision_tree.png" /></p>
@@ -4398,7 +4398,7 @@
<h3 id="1-dynamic-programming-approach">1. &nbsp; Dynamic Programming Approach<a class="headerlink" href="#1-dynamic-programming-approach" title="Permanent link">&para;</a></h3>
<p><strong>Step 1: Think about the decisions in each round, define the state, and thus obtain the <span class="arithmatex">\(dp\)</span> table</strong></p>
<p>Each round of decision involves performing one edit operation on string <span class="arithmatex">\(s\)</span>.</p>
<p>We want the problem scale to gradually decrease during the editing process, which allows us to construct subproblems. Let the lengths of strings <span class="arithmatex">\(s\)</span> and <span class="arithmatex">\(t\)</span> be <span class="arithmatex">\(n\)</span> and <span class="arithmatex">\(m\)</span> respectively. We first consider the tail characters of the two strings, <span class="arithmatex">\(s[n-1]\)</span> and <span class="arithmatex">\(t[m-1]\)</span>.</p>
<p>We want the problem size to gradually decrease during the editing process so that we can construct subproblems. Let the lengths of strings <span class="arithmatex">\(s\)</span> and <span class="arithmatex">\(t\)</span> be <span class="arithmatex">\(n\)</span> and <span class="arithmatex">\(m\)</span> respectively. We first consider the tail characters of the two strings, <span class="arithmatex">\(s[n-1]\)</span> and <span class="arithmatex">\(t[m-1]\)</span>.</p>
<ul>
<li>If <span class="arithmatex">\(s[n-1]\)</span> and <span class="arithmatex">\(t[m-1]\)</span> are the same, we can skip them and directly consider <span class="arithmatex">\(s[n-2]\)</span> and <span class="arithmatex">\(t[m-2]\)</span>.</li>
<li>If <span class="arithmatex">\(s[n-1]\)</span> and <span class="arithmatex">\(t[m-1]\)</span> are different, we need to perform one edit on <span class="arithmatex">\(s\)</span> (insert, delete, or replace) to make the tail characters of the two strings the same, allowing us to skip them and consider a smaller-scale problem.</li>
@@ -4416,7 +4416,7 @@
<p><img alt="State transition for edit distance" class="animation-figure" src="../edit_distance_problem.assets/edit_distance_state_transfer.png" /></p>
<p align="center"> Figure 14-29 &nbsp; State transition for edit distance </p>
<p>Based on the above analysis, the optimal substructure can be obtained: the minimum number of edits for <span class="arithmatex">\(dp[i, j]\)</span> equals the minimum among the minimum edit steps of <span class="arithmatex">\(dp[i, j-1]\)</span>, <span class="arithmatex">\(dp[i-1, j]\)</span>, and <span class="arithmatex">\(dp[i-1, j-1]\)</span>, plus the edit step <span class="arithmatex">\(1\)</span> for this time. The corresponding state transition equation is:</p>
<p>Based on the above analysis, we obtain the optimal substructure: the minimum number of edits for <span class="arithmatex">\(dp[i, j]\)</span> equals the minimum of <span class="arithmatex">\(dp[i, j-1]\)</span>, <span class="arithmatex">\(dp[i-1, j]\)</span>, and <span class="arithmatex">\(dp[i-1, j-1]\)</span>, plus the current edit cost of <span class="arithmatex">\(1\)</span>. The corresponding state transition equation is:</p>
<div class="arithmatex">\[
dp[i, j] = \min(dp[i, j-1], dp[i-1, j], dp[i-1, j-1]) + 1
\]</div>
@@ -4806,7 +4806,7 @@ dp[i, j] = dp[i-1, j-1]
</div>
</div>
</div>
<p>As shown in Figure 14-30, the state transition process for the edit distance problem is very similar to the knapsack problem and can both be viewed as the process of filling a two-dimensional grid.</p>
<p>As shown in Figure 14-30, the state transition process for the edit distance problem is very similar to that of the knapsack problem; both can be viewed as the process of filling a two-dimensional grid.</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:15"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><input id="__tabbed_2_11" name="__tabbed_2" type="radio" /><input id="__tabbed_2_12" name="__tabbed_2" type="radio" /><input id="__tabbed_2_13" name="__tabbed_2" type="radio" /><input id="__tabbed_2_14" name="__tabbed_2" type="radio" /><input id="__tabbed_2_15" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">&lt;1&gt;</label><label for="__tabbed_2_2">&lt;2&gt;</label><label for="__tabbed_2_3">&lt;3&gt;</label><label for="__tabbed_2_4">&lt;4&gt;</label><label for="__tabbed_2_5">&lt;5&gt;</label><label for="__tabbed_2_6">&lt;6&gt;</label><label for="__tabbed_2_7">&lt;7&gt;</label><label for="__tabbed_2_8">&lt;8&gt;</label><label for="__tabbed_2_9">&lt;9&gt;</label><label for="__tabbed_2_10">&lt;10&gt;</label><label for="__tabbed_2_11">&lt;11&gt;</label><label for="__tabbed_2_12">&lt;12&gt;</label><label for="__tabbed_2_13">&lt;13&gt;</label><label for="__tabbed_2_14">&lt;14&gt;</label><label for="__tabbed_2_15">&lt;15&gt;</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -4859,8 +4859,8 @@ dp[i, j] = dp[i-1, j-1]
<p align="center"> Figure 14-30 &nbsp; Dynamic programming process for edit distance </p>
<h3 id="3-space-optimization">3. &nbsp; Space Optimization<a class="headerlink" href="#3-space-optimization" title="Permanent link">&para;</a></h3>
<p>Since <span class="arithmatex">\(dp[i, j]\)</span> is transferred from the solutions above <span class="arithmatex">\(dp[i-1, j]\)</span>, to the left <span class="arithmatex">\(dp[i, j-1]\)</span>, and to the upper-left <span class="arithmatex">\(dp[i-1, j-1]\)</span>, forward traversal will lose the upper-left solution <span class="arithmatex">\(dp[i-1, j-1]\)</span>, and reverse traversal cannot build <span class="arithmatex">\(dp[i, j-1]\)</span> in advance, so neither traversal order is feasible.</p>
<p>For this reason, we can use a variable <code>leftup</code> to temporarily store the upper-left solution <span class="arithmatex">\(dp[i-1, j-1]\)</span>, so we only need to consider the solutions to the left and above. This situation is the same as the unbounded knapsack problem, allowing for forward traversal. The code is as follows:</p>
<p>Since <span class="arithmatex">\(dp[i, j]\)</span> depends on the states above <span class="arithmatex">\(dp[i-1, j]\)</span>, to the left <span class="arithmatex">\(dp[i, j-1]\)</span>, and at the upper-left <span class="arithmatex">\(dp[i-1, j-1]\)</span>, forward traversal will lose the upper-left state <span class="arithmatex">\(dp[i-1, j-1]\)</span>, while reverse traversal cannot construct <span class="arithmatex">\(dp[i, j-1]\)</span> in advance, so neither traversal order is suitable.</p>
<p>For this reason, we can use a variable <code>leftup</code> to temporarily store the upper-left solution <span class="arithmatex">\(dp[i-1, j-1]\)</span>, so we only need to consider the solutions to the left and above. This situation is the same as in the unbounded knapsack problem, so we can use forward traversal. The code is as follows:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="3:13"><input checked="checked" id="__tabbed_3_1" name="__tabbed_3" type="radio" /><input id="__tabbed_3_2" name="__tabbed_3" type="radio" /><input id="__tabbed_3_3" name="__tabbed_3" type="radio" /><input id="__tabbed_3_4" name="__tabbed_3" type="radio" /><input id="__tabbed_3_5" name="__tabbed_3" type="radio" /><input id="__tabbed_3_6" name="__tabbed_3" type="radio" /><input id="__tabbed_3_7" name="__tabbed_3" type="radio" /><input id="__tabbed_3_8" name="__tabbed_3" type="radio" /><input id="__tabbed_3_9" name="__tabbed_3" type="radio" /><input id="__tabbed_3_10" name="__tabbed_3" type="radio" /><input id="__tabbed_3_11" name="__tabbed_3" type="radio" /><input id="__tabbed_3_12" name="__tabbed_3" type="radio" /><input id="__tabbed_3_13" name="__tabbed_3" type="radio" /><div class="tabbed-labels"><label for="__tabbed_3_1">Python</label><label for="__tabbed_3_2">C++</label><label for="__tabbed_3_3">Java</label><label for="__tabbed_3_4">C#</label><label for="__tabbed_3_5">Go</label><label for="__tabbed_3_6">Swift</label><label for="__tabbed_3_7">JS</label><label for="__tabbed_3_8">TS</label><label for="__tabbed_3_9">Dart</label><label for="__tabbed_3_10">Rust</label><label for="__tabbed_3_11">C</label><label for="__tabbed_3_12">Kotlin</label><label for="__tabbed_3_13">Ruby</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
+22 -22
View File
@@ -6,7 +6,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code">
<meta name="description" content="Data structures and algorithms tutorial with animated illustrations and ready-to-run code">
<meta name="author" content="krahets">
@@ -576,7 +576,7 @@
<span class="md-ellipsis">
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
@@ -598,7 +598,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
</label>
@@ -1183,7 +1183,7 @@
<span class="md-ellipsis">
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
@@ -1205,7 +1205,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
</label>
@@ -1311,7 +1311,7 @@
<span class="md-ellipsis">
4.4 Memory and Cache *
4.4 Random-Access Memory and Cache *
@@ -1402,7 +1402,7 @@
<span class="md-ellipsis">
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
@@ -1424,7 +1424,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
</label>
@@ -1502,7 +1502,7 @@
<span class="md-ellipsis">
5.3 Double-Ended Queue
5.3 Deque
@@ -1593,7 +1593,7 @@
<span class="md-ellipsis">
Chapter 6. Hashing
Chapter 6. Hash Table
@@ -1615,7 +1615,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 6. Hashing
Chapter 6. Hash Table
</label>
@@ -1888,7 +1888,7 @@
<span class="md-ellipsis">
7.3 Array Representation of Tree
7.3 Array Representation of Binary Trees
@@ -2107,7 +2107,7 @@
<span class="md-ellipsis">
8.2 Building a Heap
8.2 Heap Construction Operation
@@ -2135,7 +2135,7 @@
<span class="md-ellipsis">
8.3 Top-K Problem
8.3 Top-k Problem
@@ -2493,7 +2493,7 @@
<span class="md-ellipsis">
10.2 Binary Search Insertion
10.2 Binary Search Insertion Point
@@ -2521,7 +2521,7 @@
<span class="md-ellipsis">
10.3 Binary Search Edge Cases
10.3 Binary Search Boundaries
@@ -2577,7 +2577,7 @@
<span class="md-ellipsis">
10.5 Search Algorithms Revisited
10.5 Searching Algorithms Revisited
@@ -2726,7 +2726,7 @@
<span class="md-ellipsis">
11.1 Sorting Algorithms
11.1 Sorting Algorithm
@@ -3199,7 +3199,7 @@
<span class="md-ellipsis">
12.4 Hanoi Tower Problem
12.4 Hanota Problem
@@ -4113,7 +4113,7 @@
<span class="md-ellipsis">
16.3 Terminology Table
16.3 Glossary
@@ -4279,8 +4279,8 @@
<p><img alt="Dynamic programming" class="cover-image" src="../assets/covers/chapter_dynamic_programming.jpg" /></p>
<div class="admonition abstract">
<p class="admonition-title">Abstract</p>
<p>Streams converge into rivers, rivers converge into the sea.</p>
<p>Dynamic programming gathers solutions to small problems into answers to large problems, step by step guiding us to the shore of problem-solving.</p>
<p>Streams flow into rivers, rivers flow into the sea.</p>
<p>Dynamic programming combines solutions to small problems into the answer to a large problem, leading us step by step to the other shore of problem-solving.</p>
</div>
<h2 id="chapter-contents">Chapter contents<a class="headerlink" href="#chapter-contents" title="Permanent link">&para;</a></h2>
<ul>
@@ -6,7 +6,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code">
<meta name="description" content="Data structures and algorithms tutorial with animated illustrations and ready-to-run code">
<meta name="author" content="krahets">
@@ -576,7 +576,7 @@
<span class="md-ellipsis">
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
@@ -598,7 +598,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
</label>
@@ -1183,7 +1183,7 @@
<span class="md-ellipsis">
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
@@ -1205,7 +1205,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
</label>
@@ -1311,7 +1311,7 @@
<span class="md-ellipsis">
4.4 Memory and Cache *
4.4 Random-Access Memory and Cache *
@@ -1402,7 +1402,7 @@
<span class="md-ellipsis">
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
@@ -1424,7 +1424,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
</label>
@@ -1502,7 +1502,7 @@
<span class="md-ellipsis">
5.3 Double-Ended Queue
5.3 Deque
@@ -1593,7 +1593,7 @@
<span class="md-ellipsis">
Chapter 6. Hashing
Chapter 6. Hash Table
@@ -1615,7 +1615,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 6. Hashing
Chapter 6. Hash Table
</label>
@@ -1888,7 +1888,7 @@
<span class="md-ellipsis">
7.3 Array Representation of Tree
7.3 Array Representation of Binary Trees
@@ -2107,7 +2107,7 @@
<span class="md-ellipsis">
8.2 Building a Heap
8.2 Heap Construction Operation
@@ -2135,7 +2135,7 @@
<span class="md-ellipsis">
8.3 Top-K Problem
8.3 Top-k Problem
@@ -2493,7 +2493,7 @@
<span class="md-ellipsis">
10.2 Binary Search Insertion
10.2 Binary Search Insertion Point
@@ -2521,7 +2521,7 @@
<span class="md-ellipsis">
10.3 Binary Search Edge Cases
10.3 Binary Search Boundaries
@@ -2577,7 +2577,7 @@
<span class="md-ellipsis">
10.5 Search Algorithms Revisited
10.5 Searching Algorithms Revisited
@@ -2726,7 +2726,7 @@
<span class="md-ellipsis">
11.1 Sorting Algorithms
11.1 Sorting Algorithm
@@ -3199,7 +3199,7 @@
<span class="md-ellipsis">
12.4 Hanoi Tower Problem
12.4 Hanota Problem
@@ -4205,7 +4205,7 @@
<span class="md-ellipsis">
16.3 Terminology Table
16.3 Glossary
@@ -4411,7 +4411,7 @@
<p><img alt="Number of ways to reach the 3rd step" class="animation-figure" src="../intro_to_dynamic_programming.assets/climbing_stairs_example.png" /></p>
<p align="center"> Figure 14-1 &nbsp; Number of ways to reach the 3rd step </p>
<p>The goal of this problem is to find the number of ways, <strong>we can consider using backtracking to enumerate all possibilities</strong>. Specifically, imagine climbing stairs as a multi-round selection process: starting from the ground, choosing to go up <span class="arithmatex">\(1\)</span> or <span class="arithmatex">\(2\)</span> steps in each round, incrementing the count by <span class="arithmatex">\(1\)</span> whenever the top of the stairs is reached, and pruning when exceeding the top. The code is as follows:</p>
<p>The goal of this problem is to determine the number of ways, so <strong>we can consider using backtracking to enumerate all possibilities</strong>. Specifically, imagine climbing stairs as a multi-round selection process: starting from the ground, choosing to go up <span class="arithmatex">\(1\)</span> or <span class="arithmatex">\(2\)</span> steps in each round, incrementing the count by <span class="arithmatex">\(1\)</span> whenever the top of the stairs is reached, and pruning when exceeding the top. The code is as follows:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="1:13"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><input id="__tabbed_1_11" name="__tabbed_1" type="radio" /><input id="__tabbed_1_12" name="__tabbed_1" type="radio" /><input id="__tabbed_1_13" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Python</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Java</label><label for="__tabbed_1_4">C#</label><label for="__tabbed_1_5">Go</label><label for="__tabbed_1_6">Swift</label><label for="__tabbed_1_7">JS</label><label for="__tabbed_1_8">TS</label><label for="__tabbed_1_9">Dart</label><label for="__tabbed_1_10">Rust</label><label for="__tabbed_1_11">C</label><label for="__tabbed_1_12">Kotlin</label><label for="__tabbed_1_13">Ruby</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -4801,12 +4801,12 @@ dp[i-1], dp[i-2], \dots, dp[2], dp[1]
<div class="arithmatex">\[
dp[i] = dp[i-1] + dp[i-2]
\]</div>
<p>This means that in the stair climbing problem, there exists a recurrence relation among the subproblems, <strong>the solution to the original problem can be constructed from the solutions to the subproblems</strong>. Figure 14-2 illustrates this recurrence relation.</p>
<p>This means that in the stair climbing problem, there exists a recurrence relation among the subproblems, and <strong>the solution to the original problem can be constructed from the solutions to the subproblems</strong>. Figure 14-2 illustrates this recurrence relation.</p>
<p><img alt="Recurrence relation for the number of ways" class="animation-figure" src="../intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png" /></p>
<p align="center"> Figure 14-2 &nbsp; Recurrence relation for the number of ways </p>
<p>We can obtain a brute force search solution based on the recurrence formula. Starting from <span class="arithmatex">\(dp[n]\)</span>, <strong>recursively decompose a larger problem into the sum of two smaller problems</strong>, until reaching the smallest subproblems <span class="arithmatex">\(dp[1]\)</span> and <span class="arithmatex">\(dp[2]\)</span> and returning. Among them, the solutions to the smallest subproblems are known, namely <span class="arithmatex">\(dp[1] = 1\)</span> and <span class="arithmatex">\(dp[2] = 2\)</span>, representing <span class="arithmatex">\(1\)</span> and <span class="arithmatex">\(2\)</span> ways to climb to the <span class="arithmatex">\(1\)</span>st and <span class="arithmatex">\(2\)</span>nd steps, respectively.</p>
<p>Observe the following code, which, like standard backtracking code, belongs to depth-first search but is more concise:</p>
<p>Observe the following code: like standard backtracking code, it also uses depth-first search but is more concise:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="2:13"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><input id="__tabbed_2_11" name="__tabbed_2" type="radio" /><input id="__tabbed_2_12" name="__tabbed_2" type="radio" /><input id="__tabbed_2_13" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">Python</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Java</label><label for="__tabbed_2_4">C#</label><label for="__tabbed_2_5">Go</label><label for="__tabbed_2_6">Swift</label><label for="__tabbed_2_7">JS</label><label for="__tabbed_2_8">TS</label><label for="__tabbed_2_9">Dart</label><label for="__tabbed_2_10">Rust</label><label for="__tabbed_2_11">C</label><label for="__tabbed_2_12">Kotlin</label><label for="__tabbed_2_13">Ruby</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -5027,7 +5027,7 @@ dp[i] = dp[i-1] + dp[i-2]
</div>
</div>
</div>
<p>Figure 14-3 shows the recursion tree formed by brute force search. For the problem <span class="arithmatex">\(dp[n]\)</span>, the depth of its recursion tree is <span class="arithmatex">\(n\)</span>, with a time complexity of <span class="arithmatex">\(O(2^n)\)</span>. Exponential order represents explosive growth; if we input a relatively large <span class="arithmatex">\(n\)</span>, we will fall into a long wait.</p>
<p>Figure 14-3 shows the recursion tree formed by brute force search. For the problem <span class="arithmatex">\(dp[n]\)</span>, the depth of its recursion tree is <span class="arithmatex">\(n\)</span>, with a time complexity of <span class="arithmatex">\(O(2^n)\)</span>. Exponential growth is explosive; if we input a relatively large <span class="arithmatex">\(n\)</span>, the wait can be very long.</p>
<p><img alt="Recursion tree for climbing stairs" class="animation-figure" src="../intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png" /></p>
<p align="center"> Figure 14-3 &nbsp; Recursion tree for climbing stairs </p>
@@ -5361,7 +5361,7 @@ dp[i] = dp[i-1] + dp[i-2]
</div>
</div>
</div>
<p>Observe Figure 14-4, <strong>after memoization, all overlapping subproblems only need to be computed once, optimizing the time complexity to <span class="arithmatex">\(O(n)\)</span></strong>, which is a tremendous leap.</p>
<p>Observe Figure 14-4: <strong>after memoization, all overlapping subproblems need to be computed only once, reducing the time complexity to <span class="arithmatex">\(O(n)\)</span></strong>, which is a tremendous leap.</p>
<p><img alt="Recursion tree with memoization" class="animation-figure" src="../intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png" /></p>
<p align="center"> Figure 14-4 &nbsp; Recursion tree with memoization </p>
@@ -5616,7 +5616,7 @@ dp[i] = dp[i-1] + dp[i-2]
<li>The recurrence formula <span class="arithmatex">\(dp[i] = dp[i-1] + dp[i-2]\)</span> is called the <u>state transition equation</u>.</li>
</ul>
<h2 id="1414-space-optimization">14.1.4 &nbsp; Space Optimization<a class="headerlink" href="#1414-space-optimization" title="Permanent link">&para;</a></h2>
<p>Observant readers may have noticed that <strong>since <span class="arithmatex">\(dp[i]\)</span> is only related to <span class="arithmatex">\(dp[i-1]\)</span> and <span class="arithmatex">\(dp[i-2]\)</span>, we do not need to use an array <code>dp</code> to store the solutions to all subproblems</strong>, but can simply use two variables to roll forward. The code is as follows:</p>
<p>Observant readers may have noticed that <strong>since <span class="arithmatex">\(dp[i]\)</span> is only related to <span class="arithmatex">\(dp[i-1]\)</span> and <span class="arithmatex">\(dp[i-2]\)</span>, we do not need to use an array <code>dp</code> to store the solutions to all subproblems</strong>, and can instead use two variables that roll forward. The code is as follows:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="5:13"><input checked="checked" id="__tabbed_5_1" name="__tabbed_5" type="radio" /><input id="__tabbed_5_2" name="__tabbed_5" type="radio" /><input id="__tabbed_5_3" name="__tabbed_5" type="radio" /><input id="__tabbed_5_4" name="__tabbed_5" type="radio" /><input id="__tabbed_5_5" name="__tabbed_5" type="radio" /><input id="__tabbed_5_6" name="__tabbed_5" type="radio" /><input id="__tabbed_5_7" name="__tabbed_5" type="radio" /><input id="__tabbed_5_8" name="__tabbed_5" type="radio" /><input id="__tabbed_5_9" name="__tabbed_5" type="radio" /><input id="__tabbed_5_10" name="__tabbed_5" type="radio" /><input id="__tabbed_5_11" name="__tabbed_5" type="radio" /><input id="__tabbed_5_12" name="__tabbed_5" type="radio" /><input id="__tabbed_5_13" name="__tabbed_5" type="radio" /><div class="tabbed-labels"><label for="__tabbed_5_1">Python</label><label for="__tabbed_5_2">C++</label><label for="__tabbed_5_3">Java</label><label for="__tabbed_5_4">C#</label><label for="__tabbed_5_5">Go</label><label for="__tabbed_5_6">Swift</label><label for="__tabbed_5_7">JS</label><label for="__tabbed_5_8">TS</label><label for="__tabbed_5_9">Dart</label><label for="__tabbed_5_10">Rust</label><label for="__tabbed_5_11">C</label><label for="__tabbed_5_12">Kotlin</label><label for="__tabbed_5_13">Ruby</label></div>
<div class="tabbed-content">
<div class="tabbed-block">
@@ -5809,7 +5809,7 @@ dp[i] = dp[i-1] + dp[i-2]
</div>
</div>
</div>
<p>Observing the above code, since the space occupied by the array <code>dp</code> is saved, the space complexity is reduced from <span class="arithmatex">\(O(n)\)</span> to <span class="arithmatex">\(O(1)\)</span>.</p>
<p>As the above code shows, by eliminating the space occupied by the array <code>dp</code>, the space complexity is reduced from <span class="arithmatex">\(O(n)\)</span> to <span class="arithmatex">\(O(1)\)</span>.</p>
<p>In dynamic programming problems, the current state often depends only on a limited number of preceding states, allowing us to retain only the necessary states and save memory space through "dimension reduction". <strong>This space optimization technique is called "rolling variable" or "rolling array"</strong>.</p>
<!-- Source file information -->
@@ -6,7 +6,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code">
<meta name="description" content="Data structures and algorithms tutorial with animated illustrations and ready-to-run code">
<meta name="author" content="krahets">
@@ -576,7 +576,7 @@
<span class="md-ellipsis">
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
@@ -598,7 +598,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
</label>
@@ -1183,7 +1183,7 @@
<span class="md-ellipsis">
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
@@ -1205,7 +1205,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
</label>
@@ -1311,7 +1311,7 @@
<span class="md-ellipsis">
4.4 Memory and Cache *
4.4 Random-Access Memory and Cache *
@@ -1402,7 +1402,7 @@
<span class="md-ellipsis">
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
@@ -1424,7 +1424,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
</label>
@@ -1502,7 +1502,7 @@
<span class="md-ellipsis">
5.3 Double-Ended Queue
5.3 Deque
@@ -1593,7 +1593,7 @@
<span class="md-ellipsis">
Chapter 6. Hashing
Chapter 6. Hash Table
@@ -1615,7 +1615,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 6. Hashing
Chapter 6. Hash Table
</label>
@@ -1888,7 +1888,7 @@
<span class="md-ellipsis">
7.3 Array Representation of Tree
7.3 Array Representation of Binary Trees
@@ -2107,7 +2107,7 @@
<span class="md-ellipsis">
8.2 Building a Heap
8.2 Heap Construction Operation
@@ -2135,7 +2135,7 @@
<span class="md-ellipsis">
8.3 Top-K Problem
8.3 Top-k Problem
@@ -2493,7 +2493,7 @@
<span class="md-ellipsis">
10.2 Binary Search Insertion
10.2 Binary Search Insertion Point
@@ -2521,7 +2521,7 @@
<span class="md-ellipsis">
10.3 Binary Search Edge Cases
10.3 Binary Search Boundaries
@@ -2577,7 +2577,7 @@
<span class="md-ellipsis">
10.5 Search Algorithms Revisited
10.5 Searching Algorithms Revisited
@@ -2726,7 +2726,7 @@
<span class="md-ellipsis">
11.1 Sorting Algorithms
11.1 Sorting Algorithm
@@ -3199,7 +3199,7 @@
<span class="md-ellipsis">
12.4 Hanoi Tower Problem
12.4 Hanota Problem
@@ -4205,7 +4205,7 @@
<span class="md-ellipsis">
16.3 Terminology Table
16.3 Glossary
@@ -4405,7 +4405,7 @@
<p>In this section, we will first solve the most common 0-1 knapsack problem.</p>
<div class="admonition question">
<p class="admonition-title">Question</p>
<p>Given <span class="arithmatex">\(n\)</span> items, where the weight of the <span class="arithmatex">\(i\)</span>-th item is <span class="arithmatex">\(wgt[i-1]\)</span> and its value is <span class="arithmatex">\(val[i-1]\)</span>, and a knapsack with capacity <span class="arithmatex">\(cap\)</span>. Each item can only be selected once. What is the maximum value that can be placed in the knapsack within the capacity limit?</p>
<p>Given <span class="arithmatex">\(n\)</span> items and a knapsack with capacity <span class="arithmatex">\(cap\)</span>, where the weight and value of the <span class="arithmatex">\(i\)</span>-th item are <span class="arithmatex">\(wgt[i-1]\)</span> and <span class="arithmatex">\(val[i-1]\)</span>, respectively. Each item can be selected at most once. What is the maximum value that can fit in the knapsack under the capacity limit?</p>
</div>
<p>Observe Figure 14-17. Since item number <span class="arithmatex">\(i\)</span> starts counting from <span class="arithmatex">\(1\)</span> and array indices start from <span class="arithmatex">\(0\)</span>, item <span class="arithmatex">\(i\)</span> corresponds to weight <span class="arithmatex">\(wgt[i-1]\)</span> and value <span class="arithmatex">\(val[i-1]\)</span>.</p>
<p><img alt="Example data for 0-1 knapsack" class="animation-figure" src="../knapsack_problem.assets/knapsack_example.png" /></p>
@@ -4423,21 +4423,21 @@
<li><strong>Not putting item <span class="arithmatex">\(i\)</span></strong>: The knapsack capacity remains unchanged, and the state changes to <span class="arithmatex">\([i-1, c]\)</span>.</li>
<li><strong>Putting item <span class="arithmatex">\(i\)</span></strong>: The knapsack capacity decreases by <span class="arithmatex">\(wgt[i-1]\)</span>, the value increases by <span class="arithmatex">\(val[i-1]\)</span>, and the state changes to <span class="arithmatex">\([i-1, c-wgt[i-1]]\)</span>.</li>
</ul>
<p>The above analysis reveals the optimal substructure of this problem: <strong>the maximum value <span class="arithmatex">\(dp[i, c]\)</span> equals the larger value between not putting item <span class="arithmatex">\(i\)</span> and putting item <span class="arithmatex">\(i\)</span></strong>. From this, the state transition equation can be derived:</p>
<p>The above analysis reveals the optimal substructure of this problem: <strong>the maximum value <span class="arithmatex">\(dp[i, c]\)</span> equals the greater of the values obtained by not putting item <span class="arithmatex">\(i\)</span> into the knapsack and by putting it into the knapsack</strong>. From this, the state transition equation can be derived:</p>
<div class="arithmatex">\[
dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1])
\]</div>
<p>Note that if the weight of the current item <span class="arithmatex">\(wgt[i - 1]\)</span> exceeds the remaining knapsack capacity <span class="arithmatex">\(c\)</span>, then the only option is not to put it in the knapsack.</p>
<p><strong>Step 3: Determine boundary conditions and state transition order</strong></p>
<p>When there are no items or the knapsack capacity is <span class="arithmatex">\(0\)</span>, the maximum value is <span class="arithmatex">\(0\)</span>, i.e., the first column <span class="arithmatex">\(dp[i, 0]\)</span> and the first row <span class="arithmatex">\(dp[0, c]\)</span> are both equal to <span class="arithmatex">\(0\)</span>.</p>
<p>The current state <span class="arithmatex">\([i, c]\)</span> is transferred from the state above <span class="arithmatex">\([i-1, c]\)</span> and the state in the upper-left <span class="arithmatex">\([i-1, c-wgt[i-1]]\)</span>, so the entire <span class="arithmatex">\(dp\)</span> table is traversed in order through two nested loops.</p>
<p>The current state <span class="arithmatex">\([i, c]\)</span> transitions from the state above <span class="arithmatex">\([i-1, c]\)</span> and the upper-left state <span class="arithmatex">\([i-1, c-wgt[i-1]]\)</span>, so we can traverse the entire <span class="arithmatex">\(dp\)</span> table in forward order using two nested loops.</p>
<p>Based on the above analysis, we will next implement the brute force search, memoization, and dynamic programming solutions in order.</p>
<h3 id="1-method-1-brute-force-search">1. &nbsp; Method 1: Brute Force Search<a class="headerlink" href="#1-method-1-brute-force-search" title="Permanent link">&para;</a></h3>
<p>The search code includes the following elements.</p>
<ul>
<li><strong>Recursive parameters</strong>: state <span class="arithmatex">\([i, c]\)</span>.</li>
<li><strong>Return value</strong>: solution to the subproblem <span class="arithmatex">\(dp[i, c]\)</span>.</li>
<li><strong>Termination condition</strong>: when the item number is out of bounds <span class="arithmatex">\(i = 0\)</span> or the remaining knapsack capacity is <span class="arithmatex">\(0\)</span>, terminate recursion and return value <span class="arithmatex">\(0\)</span>.</li>
<li><strong>Termination condition</strong>: when there are no items left (<span class="arithmatex">\(i = 0\)</span>) or the remaining knapsack capacity is <span class="arithmatex">\(0\)</span>, terminate the recursion and return value <span class="arithmatex">\(0\)</span>.</li>
<li><strong>Pruning</strong>: if the weight of the current item exceeds the remaining knapsack capacity, only the option of not putting it in is available.</li>
</ul>
<div class="tabbed-set tabbed-alternate" data-tabs="1:13"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><input id="__tabbed_1_11" name="__tabbed_1" type="radio" /><input id="__tabbed_1_12" name="__tabbed_1" type="radio" /><input id="__tabbed_1_13" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1">Python</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Java</label><label for="__tabbed_1_4">C#</label><label for="__tabbed_1_5">Go</label><label for="__tabbed_1_6">Swift</label><label for="__tabbed_1_7">JS</label><label for="__tabbed_1_8">TS</label><label for="__tabbed_1_9">Dart</label><label for="__tabbed_1_10">Rust</label><label for="__tabbed_1_11">C</label><label for="__tabbed_1_12">Kotlin</label><label for="__tabbed_1_13">Ruby</label></div>
@@ -4694,7 +4694,7 @@ dp[i, c] = \max(dp[i-1, c], dp[i-1, c - wgt[i-1]] + val[i-1])
</div>
</div>
</div>
<p>As shown in Figure 14-18, since each item generates two search branches of not selecting and selecting, the time complexity is <span class="arithmatex">\(O(2^n)\)</span>.</p>
<p>As shown in Figure 14-18, since each item generates two search branches, excluding it and including it, the time complexity is <span class="arithmatex">\(O(2^n)\)</span>.</p>
<p>Observing the recursion tree, it is easy to see overlapping subproblems, such as <span class="arithmatex">\(dp[1, 10]\)</span>. When there are many items, large knapsack capacity, and especially many items with the same weight, the number of overlapping subproblems will increase significantly.</p>
<p><img alt="Brute force search recursion tree for 0-1 knapsack problem" class="animation-figure" src="../knapsack_problem.assets/knapsack_dfs.png" /></p>
<p align="center"> Figure 14-18 &nbsp; Brute force search recursion tree for 0-1 knapsack problem </p>
@@ -6,7 +6,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code">
<meta name="description" content="Data structures and algorithms tutorial with animated illustrations and ready-to-run code">
<meta name="author" content="krahets">
@@ -576,7 +576,7 @@
<span class="md-ellipsis">
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
@@ -598,7 +598,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
</label>
@@ -1183,7 +1183,7 @@
<span class="md-ellipsis">
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
@@ -1205,7 +1205,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
</label>
@@ -1311,7 +1311,7 @@
<span class="md-ellipsis">
4.4 Memory and Cache *
4.4 Random-Access Memory and Cache *
@@ -1402,7 +1402,7 @@
<span class="md-ellipsis">
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
@@ -1424,7 +1424,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
</label>
@@ -1502,7 +1502,7 @@
<span class="md-ellipsis">
5.3 Double-Ended Queue
5.3 Deque
@@ -1593,7 +1593,7 @@
<span class="md-ellipsis">
Chapter 6. Hashing
Chapter 6. Hash Table
@@ -1615,7 +1615,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 6. Hashing
Chapter 6. Hash Table
</label>
@@ -1888,7 +1888,7 @@
<span class="md-ellipsis">
7.3 Array Representation of Tree
7.3 Array Representation of Binary Trees
@@ -2107,7 +2107,7 @@
<span class="md-ellipsis">
8.2 Building a Heap
8.2 Heap Construction Operation
@@ -2135,7 +2135,7 @@
<span class="md-ellipsis">
8.3 Top-K Problem
8.3 Top-k Problem
@@ -2493,7 +2493,7 @@
<span class="md-ellipsis">
10.2 Binary Search Insertion
10.2 Binary Search Insertion Point
@@ -2521,7 +2521,7 @@
<span class="md-ellipsis">
10.3 Binary Search Edge Cases
10.3 Binary Search Boundaries
@@ -2577,7 +2577,7 @@
<span class="md-ellipsis">
10.5 Search Algorithms Revisited
10.5 Searching Algorithms Revisited
@@ -2726,7 +2726,7 @@
<span class="md-ellipsis">
11.1 Sorting Algorithms
11.1 Sorting Algorithm
@@ -3199,7 +3199,7 @@
<span class="md-ellipsis">
12.4 Hanoi Tower Problem
12.4 Hanota Problem
@@ -3785,10 +3785,10 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#1-key-review" class="md-nav__link">
<a href="#1-key-points" class="md-nav__link">
<span class="md-ellipsis">
1. &nbsp; Key Review
1. &nbsp; Key Points
</span>
</a>
@@ -4172,7 +4172,7 @@
<span class="md-ellipsis">
16.3 Terminology Table
16.3 Glossary
@@ -4287,10 +4287,10 @@
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#1-key-review" class="md-nav__link">
<a href="#1-key-points" class="md-nav__link">
<span class="md-ellipsis">
1. &nbsp; Key Review
1. &nbsp; Key Points
</span>
</a>
@@ -4335,7 +4335,7 @@
<!-- Page content -->
<h1 id="147-summary">14.7 &nbsp; Summary<a class="headerlink" href="#147-summary" title="Permanent link">&para;</a></h1>
<h3 id="1-key-review">1. &nbsp; Key Review<a class="headerlink" href="#1-key-review" title="Permanent link">&para;</a></h3>
<h3 id="1-key-points">1. &nbsp; Key Points<a class="headerlink" href="#1-key-points" title="Permanent link">&para;</a></h3>
<ul>
<li>Dynamic programming decomposes problems and avoids redundant computation by storing the solutions to subproblems, thereby significantly improving computational efficiency.</li>
<li>Without considering time constraints, all dynamic programming problems can be solved using backtracking (brute force search), but the recursion tree contains a large number of overlapping subproblems, resulting in extremely low efficiency. By introducing a memo list, we can store the solutions to all computed subproblems, ensuring that overlapping subproblems are only computed once.</li>
@@ -4343,12 +4343,12 @@
<li>Subproblem decomposition is a general algorithmic approach, with different properties in divide and conquer, dynamic programming, and backtracking.</li>
<li>Dynamic programming problems have three major characteristics: overlapping subproblems, optimal substructure, and no aftereffects.</li>
<li>If the optimal solution to the original problem can be constructed from the optimal solutions to the subproblems, then it has optimal substructure.</li>
<li>No aftereffects means that for a given state, its future development is only related to that state and has nothing to do with all past states. Many combinatorial optimization problems do not have no aftereffects and cannot be quickly solved using dynamic programming.</li>
<li>No aftereffects means that for a given state, its future development is only related to that state and has nothing to do with all past states. Many combinatorial optimization problems do not satisfy this property and cannot be solved efficiently using dynamic programming.</li>
</ul>
<p><strong>Knapsack problem</strong></p>
<ul>
<li>The knapsack problem is one of the most typical dynamic programming problems, with variants such as the 0-1 knapsack, unbounded knapsack, and multiple knapsack.</li>
<li>The state definition for the 0-1 knapsack is the maximum value among the first <span class="arithmatex">\(i\)</span> items in a knapsack of capacity <span class="arithmatex">\(c\)</span>. Based on the two decisions of not putting an item in the knapsack and putting it in, the optimal substructure can be identified and the state transition equation constructed. In space optimization, since each state depends on the state directly above and to the upper-left, the list needs to be traversed in reverse order to avoid overwriting the upper-left state.</li>
<li>The state definition for the 0-1 knapsack is the maximum value achievable using the first <span class="arithmatex">\(i\)</span> items with a knapsack capacity of <span class="arithmatex">\(c\)</span>. Based on the two decisions of not putting an item in the knapsack and putting it in, the optimal substructure can be identified and the state transition equation constructed. In space optimization, since each state depends on the state directly above and to the upper-left, the list needs to be traversed in reverse order to avoid overwriting the upper-left state.</li>
<li>The unbounded knapsack problem has no limit on the selection quantity of each type of item, so the state transition for choosing to put in an item differs from the 0-1 knapsack problem. Since the state depends on the state directly above and directly to the left, space optimization should use forward traversal.</li>
<li>The coin change problem is a variant of the unbounded knapsack problem. It changes from seeking the "maximum" value to seeking the "minimum" number of coins, so <span class="arithmatex">\(\max()\)</span> in the state transition equation should be changed to <span class="arithmatex">\(\min()\)</span>. It changes from seeking "not exceeding" the knapsack capacity to seeking "exactly" making up the target amount, so <span class="arithmatex">\(amt + 1\)</span> is used to represent the invalid solution of "unable to make up the target amount".</li>
<li>Coin change problem II changes from seeking the "minimum number of coins" to seeking the "number of coin combinations", so the state transition equation correspondingly changes from <span class="arithmatex">\(\min()\)</span> to a summation operator.</li>
@@ -6,7 +6,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Data Structures and Algorithms Crash Course with Animated Illustrations and Off-the-Shelf Code">
<meta name="description" content="Data structures and algorithms tutorial with animated illustrations and ready-to-run code">
<meta name="author" content="krahets">
@@ -576,7 +576,7 @@
<span class="md-ellipsis">
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
@@ -598,7 +598,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 1. Encounter With Algorithms
Chapter 1. Encounter with Algorithms
</label>
@@ -1183,7 +1183,7 @@
<span class="md-ellipsis">
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
@@ -1205,7 +1205,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 4. Array and Linked List
Chapter 4. Arrays and Linked Lists
</label>
@@ -1311,7 +1311,7 @@
<span class="md-ellipsis">
4.4 Memory and Cache *
4.4 Random-Access Memory and Cache *
@@ -1402,7 +1402,7 @@
<span class="md-ellipsis">
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
@@ -1424,7 +1424,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 5. Stack and Queue
Chapter 5. Stacks and Queues
</label>
@@ -1502,7 +1502,7 @@
<span class="md-ellipsis">
5.3 Double-Ended Queue
5.3 Deque
@@ -1593,7 +1593,7 @@
<span class="md-ellipsis">
Chapter 6. Hashing
Chapter 6. Hash Table
@@ -1615,7 +1615,7 @@
<span class="md-nav__icon md-icon"></span>
Chapter 6. Hashing
Chapter 6. Hash Table
</label>
@@ -1888,7 +1888,7 @@
<span class="md-ellipsis">
7.3 Array Representation of Tree
7.3 Array Representation of Binary Trees
@@ -2107,7 +2107,7 @@
<span class="md-ellipsis">
8.2 Building a Heap
8.2 Heap Construction Operation
@@ -2135,7 +2135,7 @@
<span class="md-ellipsis">
8.3 Top-K Problem
8.3 Top-k Problem
@@ -2493,7 +2493,7 @@
<span class="md-ellipsis">
10.2 Binary Search Insertion
10.2 Binary Search Insertion Point
@@ -2521,7 +2521,7 @@
<span class="md-ellipsis">
10.3 Binary Search Edge Cases
10.3 Binary Search Boundaries
@@ -2577,7 +2577,7 @@
<span class="md-ellipsis">
10.5 Search Algorithms Revisited
10.5 Searching Algorithms Revisited
@@ -2726,7 +2726,7 @@
<span class="md-ellipsis">
11.1 Sorting Algorithms
11.1 Sorting Algorithm
@@ -3199,7 +3199,7 @@
<span class="md-ellipsis">
12.4 Hanoi Tower Problem
12.4 Hanota Problem
@@ -3832,12 +3832,12 @@
<a href="#1453-coin-change-problem-ii" class="md-nav__link">
<span class="md-ellipsis">
14.5.3 &nbsp; Coin Change Problem Ii
14.5.3 &nbsp; Coin Change Problem II
</span>
</a>
<nav class="md-nav" aria-label="14.5.3 Coin Change Problem Ii">
<nav class="md-nav" aria-label="14.5.3 Coin Change Problem II">
<ul class="md-nav__list">
<li class="md-nav__item">
@@ -4311,7 +4311,7 @@
<span class="md-ellipsis">
16.3 Terminology Table
16.3 Glossary
@@ -4529,12 +4529,12 @@
<a href="#1453-coin-change-problem-ii" class="md-nav__link">
<span class="md-ellipsis">
14.5.3 &nbsp; Coin Change Problem Ii
14.5.3 &nbsp; Coin Change Problem II
</span>
</a>
<nav class="md-nav" aria-label="14.5.3 Coin Change Problem Ii">
<nav class="md-nav" aria-label="14.5.3 Coin Change Problem II">
<ul class="md-nav__list">
<li class="md-nav__item">
@@ -5300,7 +5300,7 @@ dp[i, c] = \max(dp[i-1, c], dp[i, c - wgt[i-1]] + val[i-1])
<p>This problem differs from the unbounded knapsack problem in the following two aspects regarding the state transition equation.</p>
<ul>
<li>This problem seeks the minimum value, so the operator <span class="arithmatex">\(\max()\)</span> needs to be changed to <span class="arithmatex">\(\min()\)</span>.</li>
<li>The optimization target is the number of coins rather than item value, so when a coin is selected, simply execute <span class="arithmatex">\(+1\)</span>.</li>
<li>The optimization target is the number of coins rather than item value, so when a coin is selected, simply add <span class="arithmatex">\(1\)</span>.</li>
</ul>
<div class="arithmatex">\[
dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)
@@ -5309,7 +5309,7 @@ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)
<p>When the target amount is <span class="arithmatex">\(0\)</span>, the minimum number of coins needed to make it up is <span class="arithmatex">\(0\)</span>, so all <span class="arithmatex">\(dp[i, 0]\)</span> in the first column equal <span class="arithmatex">\(0\)</span>.</p>
<p>When there are no coins, <strong>it is impossible to make up any amount <span class="arithmatex">\(&gt; 0\)</span></strong>, which is an invalid solution. To enable the <span class="arithmatex">\(\min()\)</span> function in the state transition equation to identify and filter out invalid solutions, we consider using <span class="arithmatex">\(+ \infty\)</span> to represent them, i.e., set all <span class="arithmatex">\(dp[0, a]\)</span> in the first row to <span class="arithmatex">\(+ \infty\)</span>.</p>
<h3 id="2-code-implementation_1">2. &nbsp; Code Implementation<a class="headerlink" href="#2-code-implementation_1" title="Permanent link">&para;</a></h3>
<p>Most programming languages do not provide a <span class="arithmatex">\(+ \infty\)</span> variable, and can only use the maximum value of integer type <code>int</code> as a substitute. However, this can lead to large number overflow: the <span class="arithmatex">\(+ 1\)</span> operation in the state transition equation may cause overflow.</p>
<p>Most programming languages do not provide a <span class="arithmatex">\(+ \infty\)</span> variable, and can only use the maximum value of integer type <code>int</code> as a substitute. However, this can lead to integer overflow: the <span class="arithmatex">\(+ 1\)</span> operation in the state transition equation may cause overflow.</p>
<p>For this reason, we use the number <span class="arithmatex">\(amt + 1\)</span> to represent invalid solutions, because the maximum number of coins needed to make up <span class="arithmatex">\(amt\)</span> is at most <span class="arithmatex">\(amt\)</span>. Before returning, check whether <span class="arithmatex">\(dp[n, amt]\)</span> equals <span class="arithmatex">\(amt + 1\)</span>; if so, return <span class="arithmatex">\(-1\)</span>, indicating that the target amount cannot be made up. The code is as follows:</p>
<div class="tabbed-set tabbed-alternate" data-tabs="4:13"><input checked="checked" id="__tabbed_4_1" name="__tabbed_4" type="radio" /><input id="__tabbed_4_2" name="__tabbed_4" type="radio" /><input id="__tabbed_4_3" name="__tabbed_4" type="radio" /><input id="__tabbed_4_4" name="__tabbed_4" type="radio" /><input id="__tabbed_4_5" name="__tabbed_4" type="radio" /><input id="__tabbed_4_6" name="__tabbed_4" type="radio" /><input id="__tabbed_4_7" name="__tabbed_4" type="radio" /><input id="__tabbed_4_8" name="__tabbed_4" type="radio" /><input id="__tabbed_4_9" name="__tabbed_4" type="radio" /><input id="__tabbed_4_10" name="__tabbed_4" type="radio" /><input id="__tabbed_4_11" name="__tabbed_4" type="radio" /><input id="__tabbed_4_12" name="__tabbed_4" type="radio" /><input id="__tabbed_4_13" name="__tabbed_4" type="radio" /><div class="tabbed-labels"><label for="__tabbed_4_1">Python</label><label for="__tabbed_4_2">C++</label><label for="__tabbed_4_3">Java</label><label for="__tabbed_4_4">C#</label><label for="__tabbed_4_5">Go</label><label for="__tabbed_4_6">Swift</label><label for="__tabbed_4_7">JS</label><label for="__tabbed_4_8">TS</label><label for="__tabbed_4_9">Dart</label><label for="__tabbed_4_10">Rust</label><label for="__tabbed_4_11">C</label><label for="__tabbed_4_12">Kotlin</label><label for="__tabbed_4_13">Ruby</label></div>
<div class="tabbed-content">
@@ -6071,7 +6071,7 @@ dp[i, a] = \min(dp[i-1, a], dp[i, a - coins[i-1]] + 1)
</div>
</div>
</div>
<h2 id="1453-coin-change-problem-ii">14.5.3 &nbsp; Coin Change Problem Ii<a class="headerlink" href="#1453-coin-change-problem-ii" title="Permanent link">&para;</a></h2>
<h2 id="1453-coin-change-problem-ii">14.5.3 &nbsp; Coin Change Problem II<a class="headerlink" href="#1453-coin-change-problem-ii" title="Permanent link">&para;</a></h2>
<div class="admonition question">
<p class="admonition-title">Question</p>
<p>Given <span class="arithmatex">\(n\)</span> types of coins, where the denomination of the <span class="arithmatex">\(i\)</span>-th type of coin is <span class="arithmatex">\(coins[i - 1]\)</span>, and the target amount is <span class="arithmatex">\(amt\)</span>. Each type of coin can be selected multiple times. <strong>What is the number of coin combinations that can make up the target amount?</strong> An example is shown in Figure 14-26.</p>