LeetCodeに挑戦:DP系問題 Medium (その3)

LeetCodeの方で動的計画法に関するスレッド、Dynamic Programming Questions Thread を見つけましたので、今回はその中にある Medium 問題を解いてみたいと思います。(DP系問題 Medium (その2)は こちら

322. Coin Change

(問題)異なる額面のコイン(coins)とある金額(amount)が与えられます。 その amountとなる、最小のコインの数を答えて下さい。 その amount となるコインの組み合わせが存在しない場合は、-1を返して下さい。

(例1)Input:  coins = [ 1, 2, 5 ], amount = 11
Output: 3
    説明:11 = 5 + 5 + 1

(例2)Input:  coins = [ 2 ], amount = 3
Output: -1

注意:同じ額のコインを何度使用してもかまいません。

解答例を以下に示します。

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        #(1)
        dp = [0] + [float('INF') for a in range(amount)]
        #(2)
        for c in coins:
            #(3)
            for i in range(c,amount + 1):
                #(4)
                dp[i] = min(dp[i], dp[i-c] + 1)
        #(5)
        if dp[amount] != float('INF'):
            return dp[amount]
        else:
            return -1

 今回は、動的計画法(DP)の中でもボトムアップと呼ばれる方法で解いています(一方でトップダウンという方法がありますが、それは再帰を使うので正直苦手です。。)。戦略としては、与え得られた coins の中の要素単体で amount を達成できる枚数と、coins の中の複数の要素を組み合わせて達成できる枚数の、小さい方を配列 dp に書き込んでいきます。

それでは、上記のコードを個別に見ていきます。

        #(1)
        dp = [0] + [float('INF') for a in range(amount)]

 (1)では、 配列 dp を初期化しています。dp の要素番号は金額であり、0 から amount までの要素数(つまり amount + 1)をとります。たとえば dp[0] は 金額 0 を意味し、それを達成するコインの数は 0 であるはずなので、dp[0] = 0 となります。また、最終的に知りたいのは amount を達成するためのコインの数ですので、dp[amount]の値がこの問題の解答となります。 また、dp[0] 以外の初期値を 0にしてしまうと、最終的にその 0 が残った場合、0枚のコイン でその金額を達成できる意になってしまうので、あえて INF で初期化しています(INFの作成についてはこちら)。つまり INF が残った場合は、その金額は与えられた coins ではつくることができなかったという意になります。
 以上を今回は、少し格好をつけて、、一文で書いています。

        #(2)
        for c in coins:
            #(3)
            for i in range(c,amount + 1):
                #(4)
                dp[i] = min(dp[i], dp[i-c] + 1)

(2)の forループで、与えられるコインのすべてを個別みていきます。そして(3)の forループで、今みているコイン以上( c )から、amount までの範囲で dp を更新していきます。今みているコイン以上から、と設定するのを割と忘れがちで、例えば c = 2 となった場合は、額面2のコインのことをみている訳です。そして、dp[2] には、金額2を達成するためのコインの数を記入したい訳です。そうすると 今みている額面2のコインを1枚使えば良いですよね。よってdp[2] = 1 がいい感じになりそうです。では c = 2 の場合の dp[1]はどうなるかを考えると、基本無理ですよね。額面2のコインで、1となる金額をつくることはできません。よって (3)の forループ は、c からはじめています。
(4)で、dp を更新します。dp[ i ] か dp[ i – c ] + 1 の小さい方を採用しています。今一度、dp の要素番号が何を示していたかを確認すると、金額です。よってここでは ic を用いて金額を示しています。そう考えれば dp[ i – c] の部分は今、更新したい 金額 i からコインの額面 c を引いた金額を示していることになります。そして + 1 とするのは、i – c の金額に c を足せば i に戻る( i – c + c = i)となるので、枚数として 1 を足しています。

        #(5)
        if dp[amount] == float('INF'):
            return -1
        else:
            return dp[amount]

最後に、更新を終えた dp から dp[amount] の値を参照して、INF のままであれば(与えらえた amount をつくることはできなかったので) -1 を返し、そうでなければその値を返しています。

464. Can I Win

 (問題が何を言っているかわかならいので、一旦パスします)


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です