mirror of
https://github.com/krahets/hello-algo.git
synced 2025-07-19 20:55:57 +08:00
deploy
This commit is contained in:
@ -3454,9 +3454,9 @@
|
||||
<p class="admonition-title">爬楼梯</p>
|
||||
<p>给定一个共有 <span class="arithmatex">\(n\)</span> 阶的楼梯,你每步可以上 <span class="arithmatex">\(1\)</span> 阶或者 <span class="arithmatex">\(2\)</span> 阶,请问有多少种方案可以爬到楼顶。</p>
|
||||
</div>
|
||||
<p>如下图所示,对于一个 <span class="arithmatex">\(3\)</span> 阶楼梯,共有 <span class="arithmatex">\(3\)</span> 种方案可以爬到楼顶。</p>
|
||||
<p>如图 14-1 所示,对于一个 <span class="arithmatex">\(3\)</span> 阶楼梯,共有 <span class="arithmatex">\(3\)</span> 种方案可以爬到楼顶。</p>
|
||||
<p><img alt="爬到第 3 阶的方案数量" src="../intro_to_dynamic_programming.assets/climbing_stairs_example.png" /></p>
|
||||
<p align="center"> 图:爬到第 3 阶的方案数量 </p>
|
||||
<p align="center"> 图 14-1 爬到第 3 阶的方案数量 </p>
|
||||
|
||||
<p>本题的目标是求解方案数量,<strong>我们可以考虑通过回溯来穷举所有可能性</strong>。具体来说,将爬楼梯想象为一个多轮选择的过程:从地面出发,每轮选择上 <span class="arithmatex">\(1\)</span> 阶或 <span class="arithmatex">\(2\)</span> 阶,每当到达楼梯顶部时就将方案数量加 <span class="arithmatex">\(1\)</span> ,当越过楼梯顶部时就将其剪枝。</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="1:12"><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" /><div class="tabbed-labels"><label for="__tabbed_1_1">Java</label><label for="__tabbed_1_2">C++</label><label for="__tabbed_1_3">Python</label><label for="__tabbed_1_4">Go</label><label for="__tabbed_1_5">JS</label><label for="__tabbed_1_6">TS</label><label for="__tabbed_1_7">C</label><label for="__tabbed_1_8">C#</label><label for="__tabbed_1_9">Swift</label><label for="__tabbed_1_10">Zig</label><label for="__tabbed_1_11">Dart</label><label for="__tabbed_1_12">Rust</label></div>
|
||||
@ -3789,9 +3789,9 @@ dp[i-1] , dp[i-2] , \dots , dp[2] , dp[1]
|
||||
<div class="arithmatex">\[
|
||||
dp[i] = dp[i-1] + dp[i-2]
|
||||
\]</div>
|
||||
<p>这意味着在爬楼梯问题中,各个子问题之间存在递推关系,<strong>原问题的解可以由子问题的解构建得来</strong>。下图展示了该递推关系。</p>
|
||||
<p>这意味着在爬楼梯问题中,各个子问题之间存在递推关系,<strong>原问题的解可以由子问题的解构建得来</strong>。图 14-2 展示了该递推关系。</p>
|
||||
<p><img alt="方案数量递推关系" src="../intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png" /></p>
|
||||
<p align="center"> 图:方案数量递推关系 </p>
|
||||
<p align="center"> 图 14-2 方案数量递推关系 </p>
|
||||
|
||||
<p>我们可以根据递推公式得到暴力搜索解法:</p>
|
||||
<ul>
|
||||
@ -3993,11 +3993,11 @@ dp[i] = dp[i-1] + dp[i-2]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>下图展示了暴力搜索形成的递归树。对于问题 <span class="arithmatex">\(dp[n]\)</span> ,其递归树的深度为 <span class="arithmatex">\(n\)</span> ,时间复杂度为 <span class="arithmatex">\(O(2^n)\)</span> 。指数阶属于爆炸式增长,如果我们输入一个比较大的 <span class="arithmatex">\(n\)</span> ,则会陷入漫长的等待之中。</p>
|
||||
<p>图 14-3 展示了暴力搜索形成的递归树。对于问题 <span class="arithmatex">\(dp[n]\)</span> ,其递归树的深度为 <span class="arithmatex">\(n\)</span> ,时间复杂度为 <span class="arithmatex">\(O(2^n)\)</span> 。指数阶属于爆炸式增长,如果我们输入一个比较大的 <span class="arithmatex">\(n\)</span> ,则会陷入漫长的等待之中。</p>
|
||||
<p><img alt="爬楼梯对应递归树" src="../intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png" /></p>
|
||||
<p align="center"> 图:爬楼梯对应递归树 </p>
|
||||
<p align="center"> 图 14-3 爬楼梯对应递归树 </p>
|
||||
|
||||
<p>观察上图发现,<strong>指数阶的时间复杂度是由于“重叠子问题”导致的</strong>。例如:<span class="arithmatex">\(dp[9]\)</span> 被分解为 <span class="arithmatex">\(dp[8]\)</span> 和 <span class="arithmatex">\(dp[7]\)</span> ,<span class="arithmatex">\(dp[8]\)</span> 被分解为 <span class="arithmatex">\(dp[7]\)</span> 和 <span class="arithmatex">\(dp[6]\)</span> ,两者都包含子问题 <span class="arithmatex">\(dp[7]\)</span> 。</p>
|
||||
<p>观察图 14-3 ,<strong>指数阶的时间复杂度是由于“重叠子问题”导致的</strong>。例如 <span class="arithmatex">\(dp[9]\)</span> 被分解为 <span class="arithmatex">\(dp[8]\)</span> 和 <span class="arithmatex">\(dp[7]\)</span> ,<span class="arithmatex">\(dp[8]\)</span> 被分解为 <span class="arithmatex">\(dp[7]\)</span> 和 <span class="arithmatex">\(dp[6]\)</span> ,两者都包含子问题 <span class="arithmatex">\(dp[7]\)</span> 。</p>
|
||||
<p>以此类推,子问题中包含更小的重叠子问题,子子孙孙无穷尽也。绝大部分计算资源都浪费在这些重叠的问题上。</p>
|
||||
<h2 id="1412">14.1.2 方法二:记忆化搜索<a class="headerlink" href="#1412" title="Permanent link">¶</a></h2>
|
||||
<p>为了提升算法效率,<strong>我们希望所有的重叠子问题都只被计算一次</strong>。为此,我们声明一个数组 <code>mem</code> 来记录每个子问题的解,并在搜索过程中这样做:</p>
|
||||
@ -4280,9 +4280,9 @@ dp[i] = dp[i-1] + dp[i-2]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>观察下图,<strong>经过记忆化处理后,所有重叠子问题都只需被计算一次,时间复杂度被优化至 <span class="arithmatex">\(O(n)\)</span></strong> ,这是一个巨大的飞跃。</p>
|
||||
<p>观察图 14-4 ,<strong>经过记忆化处理后,所有重叠子问题都只需被计算一次,时间复杂度被优化至 <span class="arithmatex">\(O(n)\)</span></strong> ,这是一个巨大的飞跃。</p>
|
||||
<p><img alt="记忆化搜索对应递归树" src="../intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png" /></p>
|
||||
<p align="center"> 图:记忆化搜索对应递归树 </p>
|
||||
<p align="center"> 图 14-4 记忆化搜索对应递归树 </p>
|
||||
|
||||
<h2 id="1413">14.1.3 方法三:动态规划<a class="headerlink" href="#1413" title="Permanent link">¶</a></h2>
|
||||
<p><strong>记忆化搜索是一种“从顶至底”的方法</strong>:我们从原问题(根节点)开始,递归地将较大子问题分解为较小子问题,直至解已知的最小子问题(叶节点)。之后,通过回溯将子问题的解逐层收集,构建出原问题的解。</p>
|
||||
@ -4492,9 +4492,9 @@ dp[i] = dp[i-1] + dp[i-2]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>下图模拟了以上代码的执行过程。</p>
|
||||
<p>图 14-5 模拟了以上代码的执行过程。</p>
|
||||
<p><img alt="爬楼梯的动态规划过程" src="../intro_to_dynamic_programming.assets/climbing_stairs_dp.png" /></p>
|
||||
<p align="center"> 图:爬楼梯的动态规划过程 </p>
|
||||
<p align="center"> 图 14-5 爬楼梯的动态规划过程 </p>
|
||||
|
||||
<p>与回溯算法一样,动态规划也使用“状态”概念来表示问题求解的某个特定阶段,每个状态都对应一个子问题以及相应的局部最优解。例如,爬楼梯问题的状态定义为当前所在楼梯阶数 <span class="arithmatex">\(i\)</span> 。</p>
|
||||
<p>总结以上,动态规划的常用术语包括:</p>
|
||||
|
Reference in New Issue
Block a user