題目
有一座高度是10級臺階的樓梯,從下往上走,每跨一步只能向上1級或者2級臺階。要求用程式來求出一共有多少種走法。
比如,每次走1級臺階,一共走10步,這是其中一種走法。我們可以簡寫成 1,1,1,1,1,1,1,1,1,1。
再比如,每次走2級臺階,一共走5步,這是另一種走法。我們可以簡寫成 2,2,2,2,2。
動態規劃問題建模階段
10級階梯 F(10) = F(9) + F(8)
9級階梯 F(9) = F(8) + F(7)
…
2級階梯 F(2) = 2
1級階梯 F(1) = 1
動態規劃三個重要的概念
最優子結構:F(9) 和 F(8) 是 F(10) 的最優子結構
邊界:當只有 1 或 2 級階梯時,可直接得出答案,不需要繼續簡化,稱 F(1) 和 F(2) 是問題的邊界
轉態轉移公式:F(n) = F(n-1) + F(n-2)
動態規劃求解問題階段
<?php
namespace app\index\controller;
use PHPUnit\Framework\TestCase;
class Index extends TestCase
{
/**
* 1、遞迴求解
* 遞迴求解的是一顆二叉樹,樹的節點就是需要就是需要計算得次數。樹的高度為 N-1,節點近似 2的N-1次方
* 時間複雜度:O(2^N)
*/
public function recursive(int $num)
{
if ($num < 1) {
return 0;
}
if ($num == 1) {
return 1;
}
if ($num == 2) {
return 2;
}
return $this->recursive($num - 1) + $this->recursive($num - 2);
}
/**
* 遞迴求解方法測試
*/
public function recursive_test()
{
$num = 30;
$res = $this->recursive($num);
$this->assertEquals($res, 1346269);
}
/**
* 2、備忘錄演算法:將不同引數的結果存入陣列,避免多次重複計算相同的子節點。
* 遞迴演算法的優化,優化了時間複雜度。
* 時間複雜度:O(N)
*/
public function memo(int $num, array &$map)
{
if ($num < 1) {
return 0;
}
if ($num == 1) {
return 1;
}
if ($num == 2) {
return 2;
}
if (isset($map[$num])) {
return $map[$num];
} else {
$value = $this->recursive($num - 1) + $this->recursive($num - 2);
array_merge($map,[$num,$value]);
return $value;
}
}
/**
* 備忘錄方法測試
*/
public function memo_test()
{
$num = 30;
$map = ['1'=>1, '2'=>2];
$res = $this->memo($num,$map);
$this->assertEquals($res, 1346269);
}
/**
* 3、動態規劃(Dynamic Programming)
* 上面兩種演算法都是自頂向下,這個為自底向上:
* 1級階梯 F(1) = 1
* 2級階梯 F(2) = 2
* 3級階梯 依賴 1 和 2級
* 4級階梯 依賴 2 和 3級
* ...
* 由此可見,每一次迭代,只要保留之前的兩種狀態,就可以推導新的轉態。而不需要像備忘錄那樣保留全部子狀態。
*
* 時間複雜度:O(N)
* 空間複雜度:O(1)
*/
public function dp(int $num)
{
if ($num < 1) {
return 0;
}
if ($num == 1) {
return 1;
}
if ($num == 2) {
return 2;
}
$a = 1;
$b = 2;
$res = 0;
for ($i =3; $i<=$num;$i++) {
$res = $a + $b;
$a = $b;
$b = $res;
}
return $res;
}
/**
* 動態規劃方法測試
*/
public function dp_test()
{
$num = 30;
$res = $this->dp($num);
$this->assertEquals($res,1346269);
}
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結