不管是青蛙跳台阶还是who爬楼梯,能上去就行

爬楼梯这个问题也是一个很经典的面试题,可以换各种人物动物,比如青蛙、小兔子跳台阶,张三李四爬楼梯等等。
题目会类似于下面这样:
假设你正在爬楼梯,需要 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[]来保存爬一个台阶有多少种方法:
【不管是青蛙跳台阶还是who爬楼梯,能上去就行】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 思否征文「如何“反杀”面试官?」,欢迎正在阅读的你也加入。

    推荐阅读