Головоломка фонтану Шампань


30

Порожні склянки з водою розташовані в такому порядку:

введіть тут опис зображення

Якщо налити рідину в 1-й склянку, якщо вона наповнена, то зайву рідину в рівних кількостях переливали б у склянки 2 та 3. Коли склянка 2 заповнена, зайва рідина буде переливатися на 4 і 5 тощо.

Враховуючи N літрів рідини, а максимальна ємність кожного склянки - 1 літр, дайте кількість рідини, присутній у будь-якій склянці, якщо ви спорожните N літрів рідини, наливши в скло, заповнивши функцію, getWaterInBucket(int N, int X)де X - кількість склянки. Так, наприклад, якщо я хочу мати 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 виливаються однаково на 4/5 і 3 виливають рівно на 5/6, таким чином 5 заповнюються у двічі щура 4/6 . Центральні чашки завжди наповнюються швидше, ніж зовнішні чашки. до моменту 4/6 заповнюються 8/9 заповнюються на 25%, а 7/10 залишаються порожніми.
Бред

1
Також це нагадує мені трикутник Паскаля ..
Бред

@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], тому що цифри скла починаються з 1 замість 0.
Том Панінг

1
Я думаю, що складною складовою може бути з'ясування індексів лівих та правих дітей. Якщо ви це представили, інтерв'юер просто попросить вас реалізувати ці функції. Може бути чітка формула.
Джеймс

Це дійсно елегантне рішення. Спасибі. Буду вдячний, якщо ви зможете відредагувати його, щоб додати коментарі до рядків коду, щоб пояснити, що означає кожен крок у процесі роздумів. Також кількість окулярів не обмежена 10. Це може бути що завгодно
Slartibartfast

1
Як ви знаходите лівий і правий окуляри?
kiewic

7

Ось як я відповів би на це питання в ситуації інтерв'ю (я не бачив цього питання раніше, і я не дивився на інші відповіді, поки не знайшов своє рішення):

По-перше, я спробував розібратися в цьому (який ви назвали "математичним рішенням"), і коли я дістався до скла 8, я зрозумів, що це буде важче, ніж здавалося, тому що скло 5 починає переповнюватися перед склом 4. У цей момент я вирішили піти по рекурсійному маршруту (просто FYI, багато питань інтерв'ю з програмуванням потребують вирішення рекурсії або спонукання).

Мислення рекурсивно, проблема стає набагато легшою: скільки води в склянці 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. Просто підсумуйте, що для всіх батьків. Записавши це як фрагмент пітона на тіло функції суму_пур_інто ():

# 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)

Макс () призначений для того, щоб ми не отримали негативну кількість переповнення.

Ми майже закінчили! Ми вибираємо систему координат з «у» вниз на сторінці, окуляри першого ряду дорівнюють 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)

Отже, щоб отримати фактично суму в склянці на р, використовуйте суму_in (всього, p).

З ОП це не зрозуміло, але біт про "ти не можеш додати параметри" може означати, що на оригінальне запитання потрібно відповісти з точки зору показаних скляних чисел . Це вирішується шляхом запису функції відображення від показових номерів скла до внутрішньої системи координат, що використовується вище. Це химерно, але можна використовувати або ітераційне, або математичне рішення. Легка для розуміння ітеративна функція:

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)

Тепер просто перепишіть функцію суму_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, як це підказує біноміальна функція, але середнє скло спочатку заповнюється, а візерунок порушується після цього.
Вінстон Еверт

Час не є частиною моделювання, і це не вплине на кінцеві результати.
DeadMG

4
оскільки його моделююча рідина заповнюється і витікає з келихів, я повинен був би стверджувати, що час неявно є частиною моделювання. На 5 літрів 4 і 6 будуть наполовину заповнені, а 5 - повністю наповненими. Коли додається шостий літр, він почне виливати в 8 і 9, але 7 і 10 не отримають води, оскільки 4 і 6 ще не досягли ємності. Таким чином, біноміальна функція не передбачає правильних значень.
Вінстон Еверт

3
-1, Це неправильно. Рівні не заповняться рівномірно.
dan_waterworth

Ти маєш рацію, я не вважав це. Але подумавши про це деякий час, я зрозумів, що ти прав. Не знаєте, як відкоригувати формулу, щоб врахувати це.
DeadMG
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.