[演算法] 一、爬樓梯

ajiang02發表於2020-02-15

題目

有一座高度是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 協議》,轉載必須註明作者和本文連結

相關文章