web-dev-qa-db-ja.com

シャンパンファウンテンパズル

空のコップの水は、次の順序で配置されます。

enter image description here

最初のグラスに液体がいっぱいの場合、液体を注ぐと、余分な液体がグラス2と3に等量で流れ込みます。ガラス2がいっぱいになると、余分な液体が4と5に流れ込みます。

液体のNリットルと各ガラスの最大容量が1リットルであるとすると、関数getWaterInBucket(int N, int X)に入力してガラスに注ぐことによって液体のNリットルを空にする場合、ガラスに存在する液体の量を与えます。ここでXガラス番号です。したがって、たとえば、最初に4リットル必要で、ガラス3の水を見つけたい場合、関数はgetWaterInBucket(4, 3)です。

これをプログラムでどのように解決しますか?パスカルの三角形を使って数学の解を見つけようとしました。これは機能しませんでした。私はそれをツリーであると考えたので、このgetWaterInBucket(BTree root, int N, int X)のようなパラメーターを追加し、各レベルでいくつかの再帰的な解決策を試すことができますが、この問題ではパラメーターを使用できません。明らかなこと、トリックがありますか?

30
Vrashabh Irde

あなたは注ぐことをシミュレートする必要があるだけです

void pour(double glasses[10], int glass, double quantity)
{
    glasses[glass] += quantity;
    if(glasses[glass] > 1.0)
    {
         double extra = glasses[glass] - 1.0;
         pour( glasses, left_glass(glass), extra / 2 );
         pour( glasses, right_glass(glass), extra / 2 );
         glasses[glass] = 1.0;
    }
}

double getWaterInGlass(int N, int X)
{
    double glasses[10] = {0,0,0,0,0,0};
    pour(glasses, 0, X);
    return glasses[N];
}

現状では、これは木ではありません。同じグラスに別のグラスを注ぐので、それがツリーになるのを防ぎます。

35
Winston Ewert

面接の状況でこの質問にどのように答えるかは次のとおりです(この質問を見たことがなく、解決策が見つかるまで他の答えを見ていませんでした)。

最初に、それを理解しようとしましたが(これは「数学解」と呼ばれています)、ガラス8に到達したとき、ガラス5がガラス4の前にオーバーフローし始めるので、思ったよりも難しいことに気付きました。再帰ルートをたどることに決めました(参考までに、プログラミングインタビューの質問の多くは、解決するために再帰または帰納が必要です)。

再帰的に考えると、問題ははるかに簡単になります。グラス8にはどのくらいの水が入っていますか?メガネ4と5からこぼれた量の半分(いっぱいになるまで)。もちろん、それは私たちがメガネ4と5からどれだけこぼれたかについて答えなければならないことを意味しますが、それもそれほど難しくないことがわかりました。ガラス5からどれだけ流出しましたか?ただし、グラス2とグラス3の半分はグラス5に残っていた1リットルを差し引いたものです。

これを完全に(そして乱雑に)解決すると、次のようになります。

_#include <iostream>
#include <cmath>
using namespace std;

double howMuchSpilledOutOf(int liters, int bucketId) {
    double spilledInto = 0.0;
    switch (bucketId) {
        case 1:
            spilledInto = liters; break;
        case 2:
            spilledInto = 0.5 * howMuchSpilledOutOf(liters, 1); break;
        case 3:
            spilledInto = 0.5 * howMuchSpilledOutOf(liters, 1); break;
        case 4:
            spilledInto = 0.5 * howMuchSpilledOutOf(liters, 2); break;
        case 5:
            spilledInto = 0.5 * howMuchSpilledOutOf(liters, 2) + 0.5 * howMuchSpilledOutOf(liters, 3); break;
        case 6:
            spilledInto = 0.5 * howMuchSpilledOutOf(liters, 3); break;
        default:
            cerr << "Invalid spill bucket ID " << bucketId << endl;
    }
    return max(0.0, spilledInto - 1.0);
}

double getWaterInBucket(int liters, int bucketId) {
    double contents = 0.0;
    switch (bucketId) {
        case 1:
            contents = liters; break;
        case 2:
            contents = 0.5 * howMuchSpilledOutOf(liters, 1); break;
        case 3:
            contents = 0.5 * howMuchSpilledOutOf(liters, 1); break;
        case 4:
            contents = 0.5 * howMuchSpilledOutOf(liters, 2); break;
        case 5:
            contents = 0.5 * howMuchSpilledOutOf(liters, 2) + 0.5 * howMuchSpilledOutOf(liters, 3); break;
        case 6:
            contents = 0.5 * howMuchSpilledOutOf(liters, 3); break;
        case 7:
            contents = 0.5 * howMuchSpilledOutOf(liters, 4); break;
        case 8:
            contents = 0.5 * howMuchSpilledOutOf(liters, 4) + 0.5 * howMuchSpilledOutOf(liters, 5); break;
        case 9:
            contents = 0.5 * howMuchSpilledOutOf(liters, 5) + 0.5 * howMuchSpilledOutOf(liters, 6); break;
        case 10:
            contents = 0.5 * howMuchSpilledOutOf(liters, 6); break;
        default:
            cerr << "Invalid contents bucket ID" << bucketId << endl;
    }
    return min(1.0, contents);
}

int main(int argc, char** argv)
{
    if (argc == 3) {
        int liters = atoi(argv[1]);
        int bucket = atoi(argv[2]);
        cout << getWaterInBucket(liters, bucket) << endl;
    }
    return 0;
}
_

この時点で(または私がこれを書いていたときに)、これは本番環境での理想的なソリューションではないことをインタビュアーに伝えます。howMuchSpilledOutOf()getWaterInBucket()の間には重複したコードがあります。バケットを「フィーダー」にマッピングする中央の場所が必要です。ただし、インタビューでは、実装の速度と正確さがより重要であり、実行速度と保守性よりも(特に明記されていない限り)、このソリューションの方が適しています。次に、コードをリファクタリングして本番品質と考えるものに近づけ、面接担当者に決定させます。

最後の注意:私のコードのどこかにタイプミスがあると確信しています。インタビュアーにもそのことを述べ、リファクタリングまたは単体テストを行った後は、自信が増すと思います。

7
Tom Panning

これをツリーの問題だと考えるのは難しいことですが、実際には有向グラフです。しかし、そのすべてを忘れてください。

上のガラスの下のどこかにあるガラスを考えてください。オーバーフローする可能性のある1つまたは2つのグラスがその上にあります。座標系を適切に選択すると(心配しないで、最後を参照してください)、任意のガラスの「親」ガラスを取得する関数を記述できます。

これで、ガラスからのオーバーフローに関係なく、ガラスに注がれる液体の量を取得するアルゴリズムについて考えることができます。ただし、答えは、各親に注がれる液体の量を差し引いて、各親グラスに保存されている量を2で除算したものです。すべての親について合計します。これをpython amount_poured_into()関数の本体のフラグメントとして記述します。

# p is coords of the current glass
amount_in = 0
for pp in parents(p):
    amount_in += max((amount_poured_into(total, pp) - 1.0)/2, 0)

Max()は、負のオーバーフローが発生しないようにするためのものです。

あと少しで終了です!ページの下にある「y」の座標系を選択します。最初の行のグラスは0、2番目の行は1などです。「x」の座標は上の行のグラスの下にゼロがあり、2番目の行は-1のx座標を持ちます。 +1、3行目-2、0、+ 2など。重要な点は、レベルyの左端または右端のガラスがabs(x)= yになることです。

これらすべてをpython(2.x)にまとめると、次のようになります。

def parents(p):
    """Get parents of glass at p"""

    (x, y) = p
    py = y - 1          # parent y
    ppx = x + 1         # right parent x
    pmx = x - 1         # left parent x

    if abs(ppx) > py:
        return ((pmx,py),)
    if abs(pmx) > py:
        return ((ppx,py),)
    return ((pmx,py), (ppx,py))

def amount_poured_into(total, p):
    """Amount of fluid poured into glass 'p'"""

    (x, y) = p
    if y == 0:    # ie, is this the top glass?
        return total

    amount_in = 0
    for pp in parents(p):
        amount_in += max((amount_poured_into(total, pp) - 1.0)/2, 0)

    return amount_in

def amount_in(total, p):
    """Amount of fluid left in glass p"""

    return min(amount_poured_into(total, p), 1)

したがって、pで実際にグラスの量を取得するには、amount_in(total、p)を使用します。

OPからは明確ではありませんが、「パラメーターを追加できません」ということは、元の質問にガラスnumbersで答える必要があることを意味する場合があります。これは、上で使用したガラスの番号から内部座標系へのマッピング関数を記述することによって解決されます。厄介ですが、反復または数学的ソリューションを使用できます。理解しやすい反復関数:

def p_from_n(n):
    """Get internal coords from glass 'number'"""

    for (y, width) in enumerate(xrange(1, n+1)):
        if n > width:
            n -= width
        else:
            x = -y + 2*(n-1)
            return (x, y)

ここで、上記のamount_in()関数を書き直して、グラス番号を受け入れるようにします。

def amount_in(total, n):
    """Amount of fluid left in glass number n"""

    p = p_from_n(n)
    return min(amount_poured_into(total, p), 1)
5
rzzzwilson

面白い。

これはシミュレーションのアプローチを取ります。

private void test() {
  double litres = 6;
  for ( int i = 1; i < 19; i++ ) {
    System.out.println("Water in glass "+i+" = "+getWater(litres, i));
  }
}

private double getWater(double litres, int whichGlass) {
  // Don't need more glasses than that.
  /*
   * NB: My glasses are numbered from 0.
   */
  double[] glasses = new double[whichGlass];
  // Pour the water in.
  pour(litres, glasses, 0);
  // Pull out the glass amount.
  return glasses[whichGlass-1];
}

// Simple non-math calculator for which glass to overflow into.
// Each glass overflows into this one and the one after.
// Only covers up to 10 glasses (0 - 9).
int[] overflowsInto = 
{1, 
 3, 4, 
 6, 7, 8, 
 10, 11, 12, 13, 
 15, 16, 17, 18, 19};

private void pour(double litres, double[] glasses, int which) {
  // Don't care about later glasses.
  if ( which < glasses.length ) {
    // Pour up to 1 litre in this glass.
    glasses[which] += litres;
    // How much overflow.
    double overflow = glasses[which] - 1;
    if ( overflow > 0 ) {
      // Remove the overflow.
      glasses[which] -= overflow;
      // Split between two.
      pour(overflow / 2, glasses, overflowsInto[which]);
      pour(overflow / 2, glasses, overflowsInto[which]+1);
    }
  }
}

印刷する(6リットル):

Water in glass 1 = 1.0
Water in glass 2 = 1.0
Water in glass 3 = 1.0
Water in glass 4 = 0.75
Water in glass 5 = 1.0
Water in glass 6 = 0.75
Water in glass 7 = 0.0
Water in glass 8 = 0.25
Water in glass 9 = 0.25
Water in glass 10 = 0.0
...

ほぼ正しいようです。

2
OldCurmudgeon