香槟喷泉之谜


30

空杯子的水按以下顺序排列:

在此处输入图片说明

当您将液体倒入第一个玻璃杯中时,如果充满了液体,则多余的液体将以相同的数量流入玻璃杯2和3。当玻璃2充满时,多余的液体将流入4和5,依此类推。

给定N升的液体,每个玻璃的最大容量为1升,如果通过填写getWaterInBucket(int N, int X)X代表玻璃编号的函数倒入玻璃中来倒空N升的液体,则给出任何玻璃中存在的液体量。因此,例如,如果我想开始时有4升水,而我想在玻璃杯3中找到水,则功能为getWaterInBucket(4, 3)

如何以编程方式解决此问题?我试图使用Pascal的三角形找到数学解决方案。这没有用。我认为它是一棵树,因此我可以添加一个这样的参数getWaterInBucket(BTree root, int N, int X),然后为每个级别尝试一些递归解决方案,但是此问题中不允许使用参数。有明显的东西吗?


18
我不想在一家管理公司的问题与香槟喷泉有关的公司工作……
mouviciel 2013年

您能倒入1号玻璃以外的其他玻璃吗?如果没有,则每一层中的每杯水量均相等。因此,每倒1、3、6、10 ...升,您将获得全层。如果您倒7升,则第四行有4杯,因此每杯将充满1/4。上面的所有层都已满。
GlenPeterson

5
@GlenPeterson从我的阅读方式来看,我认为它们不会平等地填补。是的,2和3会相等地填充,因为它们只有一物倒入其中,但是一旦它们充满,就将2相等地倒入4/5,将3相等地倒入5/6,因此5的填充量是4/6的两倍。中央杯子的填充速度总是比外部杯子快。到4/6已满时,8/9已满25%,而7/10仍为空。
2013年

1
此外,这让我想起了帕斯卡三角..
布拉德

@mouviciel Haha GlenPeterson-首先要倒的玻璃杯永远是1。面试官还说要使用这些信息。对于这个问题的正确答案,他似乎比我更困惑。
Slartibartfast

Answers:


35

您只需要模拟浇注,例如

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];
}

就目前而言,这不是一棵树。由于不同的玻璃杯会倒入相同的玻璃杯中,因此无法将其变成一棵树。


16
+1可以很好地观察到这不是一棵树。
Mihai Danila

2
好答案。在采访中,您应该说这可能会带来可伸缩性问题,因为它会计算所有眼镜的内容。另外,您还需要处理水从下一行玻璃杯中倒出的情况。你想return glasses[N-1],因为玻璃的数字在0.1,而不是开始
汤姆摇摄

1
我认为最具挑战性的部分可能是弄清楚左右孩子的索引。如果您提出此要求,则面试官只会要求您实现这些功能。可能有一个明确的公式。
詹姆斯

那是一个非常优雅的解决方案。谢谢。如果您可以对其进行编辑以在代码行中添加注释以解释每个步骤在思考过程中的含义,将不胜感激。而且眼镜的数量不限于10个。它可以是任何东西
Slartibartfast 2013年

1
您如何找到左右眼镜?
keewic

7

这是在面试情况下如何回答这个问题的方式(我以前从未见过这个问题,直到找到解决方案后,我才查看其他答案):

首先,我试图弄清楚(您称其为“数学解决方案”),当我到达玻璃杯8时,我意识到它会比看起来更坚韧,因为玻璃杯5在玻璃杯4之前开始溢出。决定沿递归路线走下去(仅供参考,很多编程面试问题都需要递归或归纳法来解决)。

递归思考,问题变得容易得多:玻璃杯8中有多少水?从眼镜4和5溢出的量的一半(直到充满)。当然,这意味着我们必须回答从眼镜4和5溅出了多少东西,但事实证明,这也不是一件很难的事。从玻璃杯5溢出了多少?但是,有一半的水是从2号和3号玻璃杯中溢出的,减去留在5号玻璃杯中的升。

完全解决这个问题(麻烦)是:

#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();应该有一个将桶映射到其“进料器”的中心位置。但是,在采访中,实现的速度和准确性比执行的速度和可维护性更重要(除非另有说明),因此此解决方案是可取的。然后,我将提出重构代码,使其更接近于我认为的生产质量,然后让面试官决定。

最后说明:我确定我的代码中有错字;我也要向面试官提及,并说在对其进行重构或单元测试之后,我会对此更有信心。


6
该解决方案是硬编码的示例。添加眼镜意味着在开关上添加“保护套” ...我认为这不是一个好的解决方案。
路易吉·马萨·加勒拉诺

2
@LuigiMassaGallerano-在这种情况下可以接受,因为这是面试问题;它不应该是一个完美的解决方案。面试官试图更好地了解候选人的思维过程。汤姆已经指出了this isn't the ideal solution

1
老实说不是。我可以向您保证,这种情况并非旨在进行硬编码。如果我问一个面试问题并提出一个测试案例场景,受访者提出了一个硬编码的解决方案,那么他最好准备提供一个通用的解决方案,否则他可能不会通过面试。
钻机

5

将其视为树问题是一条红鲱鱼,实际上是有向图。但是,请忘记所有这些。

想想顶杯下方任何地方的玻璃杯。它上面会有一两个玻璃杯,可能会溢出。通过适当选择坐标系(不用担心,请参阅结尾),我们可以编写一个函数来获取任何给定玻璃的“父”玻璃。

现在,我们可以考虑一种算法,该算法可以使倒入玻璃杯的液体量与玻璃杯的溢出无关。答案是,将大量液体倒入每个父母玻璃杯中,然后减去储存在每个父母玻璃杯中的储水量除以2。将其编写为amount_poured_into()函数主体的python片段:

# 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,第二行为1,依此类推。“ x”坐标在第一行玻璃下面为零,第二行的x坐标为-1和+1,第三行-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尚不清楚,但是有关“您不能添加参数”的信息可能意味着必须根据显示的玻璃编号回答原始问题。这可以通过编写从玻璃杯编号到上面使用的内部坐标系的映射函数来解决。这很奇怪,但是可以使用迭代或数学解决方案。一个易于理解的迭代函数:

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)

2

有趣。

这采用了模拟方法。

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
...

这似乎是正确的。


-1

这是二项式函数。可以使用nCr为级别中的每个玻璃发现级别N的玻璃之间的水比例。此外,级别N之前的眼镜总数是1到(N-1)的总和,您可以很容易地找到一个可用的公式。因此,给定X,您应该能够确定其液位,并使用nCr检查该液位的玻璃比例,从而确定X中的水量(如果有足够的升量可以降到X)。

其次,您使用BTree的想法很好,只是BTree是内部变量,而不是外部参数。

IOW,如果您已经在您的教育中涵盖了这一数学(在英国这里是大学之前就授课的),那么您应该能够解决这个问题而不会遇到太多问题。


1
我不认为这是二项式函数。正如二项式函数所建议的那样,它以1,2,1的比例达到第三级,但是中间的玻璃首先填充,然后图案破裂。
2013年

时间不是仿真的一部分,它不会影响最终结果。
DeadMG

4
由于其建模液体会从玻璃杯中充满并流出,因此我必须保持时间是模拟过程的一部分。5升时,4和6将充满一半,而5将全部充满。当添加第六升水时,它将开始倒入8和9,但是7和10将没有水,因为4和6尚未达到容量。因此,二项式函数将无法预测正确的值。
Winston Ewert

3
-1,这是错误的。关卡不会统一填充。
dan_waterworth

没错,我没有考虑。但是经过一会儿的思考,我意识到你是正确的。不知道如何调整公式以考虑到这一点。
DeadMG 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.