web-dev-qa-db-ja.com

変更アルゴリズムを理解する

変更を加える問題 の良い解決策を探していましたが、このコード(Python)を見つけました:

target = 200
coins = [1,2,5,10,20,50,100,200]
ways = [1]+[0]*target
for coin in coins:
    for i in range(coin,target+1):
        ways[i]+=ways[i-coin]
print(ways[target])

コードが文字通り何をするのか理解するのに問題はありませんが、なぜそれが機能するのか理解できません。誰でも助けることができますか?

18
gyosko

要素が「a」または「b」または「c」(私たちのコイン)に等しく、合計が「X」になる可能性のあるすべてのセットを取得するには、次のことができます。

  • 合計がX-aになるようなすべてのセットを取得し、それぞれに「a」を追加します。
  • 合計がX-bになるようなすべてのセットを取得し、それぞれに「b」を追加します。
  • 合計がX-cになるようなすべてのセットを取得し、それぞれに「c」を追加します。

したがって、Xを取得できる方法の数は、X-aとX-bおよびX-cを取得できる方法の数の合計です。

ways[i]+=ways[i-coin]

残りは単純な再発です。

追加のヒント:最初は、合計ゼロを1つの方法で設定できます(空のセット)

ways = [1]+[0]*target
14
fsw

これは 動的計画法 の古典的な例です。キャッシュを使用して、3 + 2 = 5のようなものを2回カウントするという落とし穴を回避します(別の可能な解決策:2 + 3のため)。再帰的アルゴリズムはその落とし穴に陥ります。簡単な例に焦点を当てましょう。ここで、target = 5およびcoins = [1,2,3]。あなたが投稿したコードは重要です:

  1. 3 + 2
  2. 3 + 1 + 1
  3. 2 + 2 + 1
  4. 1 + 2 + 1 + 1
  5. 1 + 1 + 1 + 1 + 1

再帰バージョンは重要ですが:

  1. 3 + 2
  2. 2 + 3
  3. 3 + 1 + 1
  4. 1 + 3 + 1
  5. 1 + 1 + 3
  6. 2 + 1 + 2
  7. 1 + 1 + 2
  8. 2 + 2 + 1
  9. 2 + 1 + 1 + 1
  10. 1 + 2 + 1 + 1
  11. 1 + 1 + 2 + 1
  12. 1 + 1 + 1 + 2
  13. 1 + 1 + 1 + 1 + 1

再帰コード:

coinsOptions = [1, 2, 3]
def numberOfWays(target):
    if (target < 0):
        return 0
    Elif(target == 0):
        return 1
    else:
        return sum([numberOfWays(target - coin) for coin in coinsOptions])
print numberOfWays(5)

動的計画法:

target = 5
coins = [1,2,3]
ways = [1]+[0]*target
for coin in coins:
    for i in range(coin, target+1):
        ways[i]+=ways[i-coin]
print ways[target]
10
Adam Kurkiewicz

コードの背後にある主なアイデアは次のとおりです。「各ステップで、コインに与えられたways金額を変更するi方法があります_[1,...coin]_」。

したがって、最初の反復では、_1_の額面のコインしかありません。どのターゲットに対してもこれらのコインだけを使用して変更を加える方法は1つしかないことは明らかだと思います。このステップでは、ways配列は_[1,...1]_のようになります(_0_からtargetまでのすべてのターゲットに対して一方向のみ)。

次のステップでは、前のコインのセットに_2_の額面のコインを追加します。これで、_0_からtargetまでの各ターゲットに存在する変更の組み合わせの数を計算できます。明らかに、組み合わせの数は、ターゲット> = _2_(または一般的な場合は新しいコインが追加された)に対してのみ増加します。したがって、ターゲットが_2_に等しい場合、組み合わせの数はways[2](old) + ways[0](new)になります。一般に、iが導入された新しいコインと等しくなるたびに、以前の組み合わせの数に_1_を追加する必要があります。新しい組み合わせは1つのコインのみで構成されます。

target> _2_の場合、回答は「すべてのコインがtarget未満のcoin金額のすべての組み合わせ」と「すべてのコインがcoin未満のcoin金額のすべての組み合わせ」の合計で構成されます。

ここでは2つの基本的な手順を説明しましたが、簡単に一般化できることを願っています。

_target = 4_と_coins=[1,2]_の計算木を示しましょう。

コインを与えられた方法[4] = [1,2] =

コインが与えられた方法[4] = [1] +コインが与えられた方法[2] = [1,2] =

1+ウェイ[2]与えられたコイン= [1] +ウェイ[0]与えられたコイン= [1,2] =

1 + 1 + 1 = 3

したがって、変更を加えるには3つの方法があります:_[1,1,1,1], [1,1,2], [2,2]_。

上記のコードは、再帰的ソリューションと完全に同等です。 再帰的解決策 を理解していれば、上記の解決策を理解しているに違いありません。

4

あなたが投稿した解決策は、このコードの要約版です。

    /// <summary>
    /// We are going to fill the biggest coins one by one.
    /// </summary>
    /// <param name="n"> the amount of money </param>
    public static void MakeChange (int n)
    {
        int n1, n2, n3; // residual of amount after each coin
        int quarter, dime, nickel; // These are number of 25c, 10c, 5c, 1c
        for (quarter = n/25; quarter >= 0; quarter--)
        {
            n1 = n - 25 * quarter;
            for (dime = n1/10; dime >= 0; dime--)
            {
                n2 = n1 - 10 * dime;
                for (nickel = n2/5; nickel >= 0 && (n2 - 5*nickel) >= 0; nickel--)
                {
                    n3 = n2 - 5 * nickel;
                    Console.WriteLine("{0},{1},{2},{3}", quarter, dime, nickel, n3); // n3 becomes the number of cent.
                }
            }
        }
    }
0
Aerin