第107回【JavaScript】階段の上り方 1、階段の上り方 2

現在取り組んでいるのは、paiza ラーニング問題集「DPメニュー」になります。

はじめに

猫とキャンプと野球観戦と AWS が大好きな旦那、LeoSaki です。モフモフしたい。

JavaScript をゼロから勉強してみよう、のコーナー 107 回目です。

とあるメーカーさんに問合せをしたのですが、その返信がありません。回答に時間がかかるのならば、せめて一次返信、問合せを受け取ったよ、くらいは教えてほしいものです。もう 1 回問合せをし直すべきか、もうしばらく待つべきか、とても悩んでいます。(3 日経過)

それでは、今日も頑張ってみようと思います。

階段の上り方 1 (paizaランク B 相当)

整数 n が与えられます。
階段を上るのに、1 歩で 1 段または 2 段を上ることができるとき、n 段の階段を上る方法は何通りあるでしょうか。


(ヒント)

これまでは問題文中に具体的な漸化式が書かれていましたが、この問題にはありません。自分で漸化式を立てる必要があります。

部分問題として、1 ~ n-1 段の階段を上る方法が何通りあるか、という問題を考えてみましょう。この部分問題の答えが求まっているとして、n 段の階段を上る方法が何通りあるかを考えてみましょう。n 段目に到達するには、n-1 段目から1段上る方法と、n-2 段目から2段上る方法の2種類が考えられます。dp[n] を n 段の階段を上る方法の数とすれば、この関係は dp[n] = dp[n-1] + dp[n-2] で表すことが出来ます。よって、0段の階段を上る方法が1通り (何もしない) であることを踏まえると、以下のようにして答えを求めることが出来ます。

dp[0] <- 1

for i = 1 to n
    dp[i] <- 0
    if i >= 1 then
        dp[i] <- dp[i] + dp[i-1]    // i-1 段目から1段上って i 段へ到達
    if i >= 2 then
        dp[i] <- dp[i] + dp[i-2]    // i-2 段目から2段上って i 段へ到達

print dp[n]

このような場合分けをすると上で考察した漸化式を満たす配列が実現できます (ピンとこなければ、i に具体的な値を入れて dp[i] がどのように計算されるのか、その処理を追ってみましょう) 。この場合分けは今のところ冗長に見えますが、次の問題を解くときに活きてきます。


入力される値

n

入力値最終行の末尾に改行が1つ入ります。


期待する出力

n 段の階段を上る方法の数を1行に出力してください。

また、末尾に改行を入れ、余計な文字、空行を含んではいけません。


すべてのテストケースにおいて、以下の条件をみたします。

・ 1 ≦ n ≦ 40


入力例

3

出力例

3

DP 問題の本丸に乗り込んできた感じ。理解出来ると、とても面白い。

JavaScript
process.stdin.resume();
process.stdin.setEncoding('utf8');

var lines = [];
var reader = require('readline').createInterface({
  input: process.stdin,
  output: process.stdout
});
reader.on('line', (line) => {
  lines.push(line);
});
reader.on('close', () => {
  const n = Number(lines[0]);
  const dp = Array(n+1);
  dp.fill(1);
  for (let i = 1; i <= n; i++) {
      dp[i] = 0;
      if (i >= 1) dp[i] += dp[i-1];
      if (i >= 2) dp[i] += dp[i-2];
  }
  console.log(dp[n]);
});
Python
n = int(input())
dp = [1] * (n+1)
for i in range(1,n+1):
    dp[i] = 0
    if i >= 1:
        dp[i] = dp[i] + dp[i-1]
    if i >= 2:
        dp[i] = dp[i] + dp[i-2]
print(dp[n])

階段の上り方 2 (paizaランク B 相当)

整数 n, a, b が与えられます。
階段を上るのに、1歩で a 段または b 段を上ることができるとき、n 段の階段を上る方法は何通りあるでしょうか。


(ヒント)

前問とやることは同じです。ただ、n, a, b の値によっては答えが0になることがあるので注意しましょう。例えば、n = 4, a = 3, b = 5 のとき、答えは0です。(1歩で3段か5段上ることができるとき、ちょうど4段の階段を上る方法は存在しない)


入力される値

n a b

入力値最終行の末尾に改行が1つ入ります。


期待する出力

n 段の階段を上る方法の数を1行に出力してください。

また、末尾に改行を入れ、余計な文字、空行を含んではいけません。


すべてのテストケースにおいて、以下の条件をみたします。

・ 1 ≦ n ≦ 40

・ 1 ≦ a ≦ 5

・ 1 ≦ b ≦ 5

・ a ≠ b


入力例

11 3 4

出力例

3

さっきの問題の応用。しかし、a と b の値によっては、到達するパターンがない可能性、解答が 0 になる可能性があることに注意しなければならない。

JavaScript
process.stdin.resume();
process.stdin.setEncoding('utf8');

var lines = [];
var reader = require('readline').createInterface({
  input: process.stdin,
  output: process.stdout
});
reader.on('line', (line) => {
  lines.push(line);
});
reader.on('close', () => {
  const [n,a,b] = lines[0].split(/\s/).map(Number);
  const dp = Array(n+1).fill(0);
  dp[0] = 1;
  for (let i = 1; i <= n; i++) {
      if (i >= a) dp[i] += dp[i-a];
      if (i >= b) dp[i] += dp[i-b];
  }
  console.log(dp[n]);
});
Python
n,a,b = map(int,input().split())
dp = [0] * (n+1)
dp[0] = 1
for i in range(1,n+1):
    if i >= a:
        dp[i] = dp[i] + dp[i-a]
    if i >= b:
        dp[i] = dp[i] + dp[i-b]
print(dp[n])

最後に

多分、ゆっくり整理して考えれば、やっていることは単純で、理解出来るのだと思います。しかし、それをコードとして書くのはまた別の問題。こういう解き方を知っていれば、幾らでも応用が利きます。

JavaScript において、配列の初期値を設定する方法として fill を利用していますが、速度的にどうなんでしょう。Python だと、None を初期値として配列を作ると早い、という話がありますが・・・。

引き続き、よろしくお願いいたします!

Python の第107回はこちら