不管是青蛙跳臺階還是who爬樓梯,能上去就行

沈唁發表於2022-04-01

爬樓梯這個問題也是一個很經典的面試題,可以換各種人物動物,比如青蛙、小兔子跳臺階,張三李四爬樓梯等等。

題目會類似於下面這樣:

假設你正在爬樓梯,需要 n 階你才能到達樓頂,每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢?

假設有 2 個臺階,那麼有兩種方法可以爬到樓頂:

  1. 1 個臺階 + 1 個臺階
  2. 2 個臺階

假設有 3 個臺階,那麼有三種方法可以爬到樓頂:

  1. 1 個臺階 + 1 個臺階 + 1 個臺階
  2. 1 個臺階 + 2 個臺階
  3. 2 個臺階 + 1 個臺階

假設有 5 個臺階,那麼有八種方法可以爬到樓頂:

  1. 1 個臺階 + 1 個臺階 + 1 個臺階 + 1 個臺階 + 1 個臺階
  2. 1 個臺階 + 1 個臺階 + 1 個臺階 + 2 個臺階
  3. 1 個臺階 + 2 個臺階 + 2 個臺階
  4. 2 個臺階 + 2 個臺階 + 1 個臺階
  5. 2 個臺階 + 1 個臺階 + 2 個臺階
  6. 2 個臺階 + 1 個臺階 + 1 個臺階 + 1 個臺階
  7. 1 個臺階 + 2 個臺階 + 1 個臺階 + 1 個臺階
  8. 1 個臺階 + 1 個臺階 + 2 個臺階 + 1 個臺階

有了三個例子,我們來倒推一下,變成程式碼的形式。

假設爬到樓頂的方法為f(n),根據題目意思有兩種可能

  • 爬一個臺階:f(n-1)
  • 爬兩個臺階:f(n−2)

只有這兩種可能,將這兩種可能相加,可以得到遞推公式:

f(n) = f(n-1) + f(n-2)

這樣我們就可以使用遞迴來實現:

但在遞迴時也需要處理邊界問題,比如f(1) = 1f(2) = 2,這兩種情況我們可以直接返回,而不需要遞迴。

所以可以得到這樣的遞迴函式:

function climbStairs($n) {
    if ($n == 1) return 1;
    if ($n == 2) return 2;
    return climbStairs($n - 1) + climbStairs($n - 2);
}

echo climbStairs(2); // 2
echo climbStairs(3); // 3
echo climbStairs(5); // 8

再來看看使用迴圈是否能解決問題?

假設我們使用迴圈來實現,那麼可以使用一個陣列來儲存已經計算過的值,假設使用一個陣列dp[]來儲存爬一個臺階有多少種方法:

dp[n] = dp[n-1] + dp[n-2]

和遞迴一樣,也滿足dp[1] = 1, dp[2] = 2,可以得到以下函式

function climbStairs($n) {
    $dp = [];
    $dp[1] = 1;
    $dp[2] = 2;
    for ($i = 3; $i <= $n; $i++) {
        $dp[$i] = $dp[$i - 1] + $dp[$i - 2];
    }
    return $dp[$n];
}

在使用遞迴時,我們是從最後一個臺階開始,朝著底下的臺階去找,並不確實下一個是不是終點,只能一個一個去找。

而在使用迴圈時,我們是從第一個臺階開始,往上爬,這個時候是知道前面的方法個數的,直接拿來用就好了,不用再重新算了。

echo climbStairs(3), PHP_EOL; // 3
echo climbStairs(4), PHP_EOL; // 5
echo climbStairs(5), PHP_EOL; // 8
echo climbStairs(6), PHP_EOL; // 13
echo climbStairs(7), PHP_EOL; // 21
echo climbStairs(8), PHP_EOL; // 34

上面的列舉也可以驗證我們的猜想,實際上我們只需要知道:當前方法個數 = 前一個方法個數 + 前前一個方法個數

假設f為當前方法個數,ab分別為前一個前前一個的方法個數,可以得到一個新的迴圈方式:

我們是從第 0 級開始爬的,從第 0 個到第 0 個臺階,我們也可以當成一種方法,即f=1

function climbStairs($n) {
    $a = $b = 0;
    $f = 1;

    for ($i = 1; $i <= $n; $i++) {
        $a = $b;
        $b = $f;
        $f = $a + $b;
    }
    return $f;
}

以上就是一個動態規劃的程式碼,直接看的話可能比較難懂為什麼這麼寫,經過遞迴和迴圈的驗證與理解,看上去就很簡單了。

在面試中遇到演算法題時不要慌,可以先從簡單暴力的方法入手,再嘗試有沒有最優解。

同時也建議有空時可以去力扣刷刷演算法題,理解一些相關概念和思路,這樣就不會在遇到演算法題時恐慌,不知道該從何處入手了。

除此之外也要熟悉所用的語言,特別是內建的一些函式方法,比如驗證一個數是不是迴文數時,PHP 就可以使用 strrev 函式,將它進行反轉後再進行比較就可以直接判斷是與否了。

$n1 = 21;
$n2 = 22;
$n3 = -22;
var_dump(strrev($n1) == $n1);
var_dump(strrev($n2) == $n2);
var_dump(strrev($n3) == $n3);

面試後記得及時覆盤,下次肯定比這次更好~

本文參與了 SegmentFault 思否徵文「如何“反殺”面試官?」,歡迎正在閱讀的你也加入。

相關文章