샴페인 분수 퍼즐


30

빈 물 잔은 다음 순서로 배열됩니다.

여기에 이미지 설명을 입력하십시오

액체가 가득 차면 첫 번째 유리에 액체를 부을 때 여분의 액체가 같은 양으로 유리 2와 3에 흘러 들어갑니다. 유리 2가 가득 차면 여분의 액체가 4와 5 등으로 흘러 들어갑니다.

N 리터의 액체와 각 유리의 최대 용량이 1 리터 인 경우 getWaterInBucket(int N, int X)X가 유리 수인 함수를 채워 유리에 부어 N 리터의 액체를 비울 경우 유리에있는 액체의 양을 지정하십시오. 예를 들어 처음에 4 리터를 원하고 유리 3에서 물을 찾으려면 함수는getWaterInBucket(4, 3)

프로그래밍 방식으로이 문제를 어떻게 해결합니까? 파스칼의 삼각형을 사용하여 수학 솔루션을 찾으려고했습니다. 이것은 작동하지 않았다. 나는 이것을 트리로 간주하여 이와 같은 매개 변수를 추가 한 getWaterInBucket(BTree root, int N, int X)다음 각 수준마다 재귀 솔루션을 시도 할 수 있지만이 문제에서는 매개 변수를 사용할 수 없습니다. 몇 가지 분명한 것이 있습니까?


18
샴페인 분수에 관한 경영상의 문제가있는 회사에서 일하고 싶지는 않습니다.
mouviciel

유리 1 이외의 유리에 부을 수 있습니까? 그렇지 않다면, 각 층은 각 유리에 같은 양의 물을 가질 것입니다. 따라서 1, 3, 6, 10 ... 리터를 부을 때마다 전체 계층을 갖게됩니다. 7 리터를 부으면 네 번째 줄에 4 개의 안경이 있으므로 각 잔은 1/4 가득합니다. 그 위의 모든 계층이 가득 찰 것입니다.
GlenPeterson

5
@GlenPeterson 나는 그것을 읽는 방법에서 그들이 똑같이 채울 것이라고 생각하지 않습니다. 예, 2 & 3은 쏟아지는 것이 하나뿐이기 때문에 똑같이 채워지지만 일단 가득 차면 2/5가 4/5에 동일하게 부어지고 3은 5/6에 동일하게 부어집니다. 따라서 5는 4/6 쥐의 두 배에 채워집니다 . 중앙 컵은 항상 외부 컵보다 빠르게 채워집니다. 4/6이 꽉 찼을 때 8/9는 25 % 꽉 차고 7/10은 여전히 ​​비어 있습니다.
Brad

1
또한 이것은 파스칼의 삼각형을 생각 나게합니다 ..
Brad

@mouviciel Haha GlenPeterson – 부어 질 유리는 항상 유리 1입니다. 면접관은 또한이 정보를 사용한다고 말했다. 그는이 문제에 대한 정답에 관한 것보다 더 혼란스러워 보였다.
Slartibartfast

답변:


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부터 시작하기 때문에 원합니다.
Tom Panning

1
나는 도전적인 부분이 왼쪽과 오른쪽 아이들의 색인을 알아내는 것이라고 생각합니다. 당신이 이것을 제시하면 면접관은 당신에게 그 기능을 구현하도록 요구할 것입니다. 명시적인 수식이있을 수 있습니다.
제임스

그것은 정말 우아한 솔루션입니다. 감사. 사고 과정에서 각 단계가 의미하는 바를 설명하기 위해 코드 줄에 주석을 추가하도록 편집 할 수 있다면 감사하겠습니다. 또한 안경의 수는 10 개로 제한되지 않습니다. 무엇이든 가능합니다
Slartibartfast

1
왼쪽 안경과 오른쪽 안경을 어떻게 찾습니까?
kiewic

7

인터뷰 상황 에서이 질문에 대답하는 방법은 다음과 같습니다 (이 질문은 이전에 보지 못했지만 해결책이있을 때까지 다른 답변을 보지 않았습니다).

먼저, 나는 그것을 알아 내려고 노력했습니다 (당신이 "수학 솔루션"이라고 불렀습니다). 그리고 나는 유리 8에 도착했을 때 유리 5가 유리 4보다 먼저 넘치기 시작하는 것보다 그것이 힘들다는 것을 깨달았습니다. 재귀 경로를 내려 가기로 결정했습니다 (FYI, 많은 프로그래밍 인터뷰 질문에는 재귀 또는 유도가 필요합니다).

재귀 적으로 생각하면 문제가 훨씬 쉬워집니다. 유리 8에 물이 얼마나 있습니까? 안경 4와 5에서 쏟아진 양의 절반 (만일 때까지). 물론 그것은 우리가 안경 4와 5에서 얼마나 많이 쏟아 졌는지에 대한 대답을해야한다는 것을 의미하지만, 그렇게 어려운 것도 아닙니다. 유리 5에서 얼마나 쏟아 졌습니까? 그러나 유리 5에 남아있는 리터를 빼고 안경 2와 3에서 많은 양의 액체가 쏟아졌습니다.

이것을 완전히 (그리고 지저분한) 해결하면 다음이 가능합니다.

#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
이 솔루션은 예제를 위해 하드 코딩되었습니다. 안경을 추가한다는 것은 스위치에 "케이스"를 추가하는 것을 의미합니다 ... 좋은 해결책이라고 생각하지 않습니다.
Luigi Massa Gallerano

2
@LuigiMassaGallerano-인터뷰 질문이므로 괜찮습니다. 완벽한 솔루션이 아닙니다. 면접관은 응시자의 사고 과정을 더 잘 이해하려고 노력하고 있습니다. 톰은 이미 지적했다 this isn't the ideal solution.

1
솔직히 그렇지 않습니다. 이 시나리오는 하드 코딩되지 않았 음을 확신 할 수 있습니다. 인터뷰 질문을하고 인터뷰 대상자가 하드 코딩 된 솔루션을 제시 한 테스트 사례 시나리오를 마련한 경우 일반적인 솔루션을 제공하는 것이 좋습니다. 그렇지 않으면 인터뷰를 통과하지 못할 수도 있습니다.
Rig

5

이것을 트리 문제로 생각하는 것은 붉은 청어입니다. 그것은 실제로 방향 그래프입니다. 그러나 그것에 대해 모든 것을 잊어라.

상단 유리 아래 어디에서나 유리를 생각하십시오. 오버플로 될 수있는 하나 이상의 유리가 위에있을 것입니다. 적절한 좌표 시스템을 선택하면 (걱정하지 말고 끝을 참조하십시오) 우리는 주어진 유리에 대해 "부모"안경을 얻는 기능을 작성할 수 있습니다.

이제 유리의 오버플로에 관계없이 유리에 액체의 양을 붓는 알고리즘을 생각할 수 있습니다. 그러나 대답은 많은 양의 액체를 각 모 유리에 저장된 양에서 2로 나눈 값을 뺀 것입니다. 이를 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, 두 번째 행은 1 등의 좌표계를 선택합니다. 'x'좌표는 상단 행 글래스 아래에 0이 있고 두 번째 행에는 x 좌표가 -1이고 +1, 세 번째 행 -2, 0, +2 등. 중요한 점은 레벨 y의 가장 왼쪽 또는 오른쪽 유리에 abs (x) = y가 있다는 것입니다.

이 모든 것을 파이썬 (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

이항 함수입니다. 레벨 N의 유리 사이의 물의 비율은 레벨의 각 유리에 대해 nCr을 사용하여 발견 할 수 있습니다. 또한 레벨 N 이전의 총 안경 수는 1에서 (N-1)의 합으로, 상당히 쉽게 구할 수있는 공식입니다. 따라서 X가 주어지면 레벨을 결정할 수 있고 nCr을 사용하여 해당 레벨에 대한 안경의 비율을 확인하고 X로 내려갈 리터가 충분하다면 X에 얼마나 많은 물이 있는지 결정해야합니다.

둘째, BTREE를 사용하여 당신의 생각은 그것은 BTREE 내부 변수입니다 단지입니다, 괜찮 되지 외부 매개 변수입니다.

IOW, 만약 당신이 당신의 교육에서이 수학을 다루었다면 (여기 영국에서는 대학보다 먼저 가르친다), 너무 많은 문제없이 이것을 해결할 수있을 것입니다.


1
나는 그것이 이항 함수라고 생각하지 않습니다. 이항 함수에서 알 수 있듯이 1,2,1의 비율로 세 번째 수준에 도달하지만 중간 유리가 먼저 채워지고 그 후에 패턴이 깨집니다.
Winston Ewert

시간은 시뮬레이션의 일부가 아니며 최종 결과에 영향을 미치지 않습니다.
DeadMG

4
모델링 액체가 채워지고 안경에서 흘러 나오기 때문에 시간이 암시 적으로 시뮬레이션의 일부임을 유지해야합니다. 5 리터에서는 4와 6이 반으로 가득 차고 5는 모두 가득 차게됩니다. 6 리터가 추가되면 8 & 9로 쏟아지기 시작하지만 4 & 6이 아직 용량에 도달하지 않아 7 & 10에 물이 공급되지 않습니다. 따라서 이항 함수는 정확한 값을 예측하지 않습니다.
Winston Ewert

3
-1, 이것이 잘못되었습니다. 레벨이 균일하게 채워지지 않습니다.
dan_waterworth

당신 말이 맞아요, 나는 그것을 고려하지 않았습니다. 그러나 한동안 생각한 후에는 당신이 옳다는 것을 깨달았습니다. 이를 고려하여 수식을 조정하는 방법을 잘 모르겠습니다.
DeadMG
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.