ปริศนาน้ำพุ Champaign


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
นอกจากนี้สิ่งนี้ทำให้ฉันนึกถึงสามเหลี่ยมของ Pascal ..
แบรด

@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 สำหรับการสังเกตที่ดีว่านี่ไม่ใช่ต้นไม้
หมดเวลา Danila

2
คำตอบที่ดี. ในการสัมภาษณ์คุณควรบอกว่าสิ่งนี้อาจมีปัญหาเรื่องความสามารถในการปรับขนาดได้เนื่องจากมันจะคำนวณเนื้อหาของแว่นตาทั้งหมด นอกจากนี้คุณต้องจัดการกับกรณีที่น้ำไหลออกจากแถวด้านล่างของแว่นตา และคุณต้องการreturn glasses[N-1]เพราะตัวเลขแก้วเริ่มต้นที่ 1 แทน 0
Tom Panning

1
ฉันคิดว่าส่วนที่ท้าทายอาจจะหาดัชนีเด็กซ้ายและขวา หากคุณนำเสนอสิ่งนี้ผู้สัมภาษณ์จะขอให้คุณใช้ฟังก์ชั่นเหล่านั้น อาจมีสูตรที่ชัดเจน
James

นั่นเป็นทางออกที่สง่างามจริงๆ ขอบคุณ ฉันจะขอบคุณถ้าคุณสามารถแก้ไขเพื่อเพิ่มความคิดเห็นในบรรทัดของรหัสเพื่ออธิบายสิ่งที่แต่ละขั้นตอนมีความหมายในกระบวนการคิด นอกจากนี้จำนวนของแว่นตาไม่ได้ จำกัด อยู่ที่ 10 มันอาจเป็นอะไรก็ได้
Slartibartfast

1
คุณจะหาแว่นตาซ้ายและขวาได้อย่างไร?
kiewic

7

นี่คือวิธีที่ฉันจะตอบคำถามนี้ในสถานการณ์สัมภาษณ์ (ฉันไม่เคยเห็นคำถามนี้มาก่อนและฉันไม่ได้ดูคำตอบอื่น ๆ จนกว่าฉันจะได้คำตอบ):

อันดับแรกฉันพยายามคิดออก (ซึ่งคุณเรียกว่า "วิธีแก้ปัญหาทางคณิตศาสตร์") และเมื่อฉันไปที่แก้ว 8 ฉันรู้ว่ามันจะยากกว่าที่คิดเพราะแก้ว 5 เริ่มล้นก่อนแก้ว 4 ณ จุดนั้นฉัน ตัดสินใจที่จะลงเส้นทางการสอบถามซ้ำ (เป็นเพียง FYI คำถามการสัมภาษณ์การเขียนโปรแกรมจำนวนมากต้องการการสอบถามซ้ำหรือการชักนำให้แก้ปัญหา)

การคิดแบบวนซ้ำปัญหาจะง่ายขึ้นมาก: น้ำในแก้ว 8 มีมากแค่ไหน? ครึ่งหนึ่งของจำนวนเงินที่แตกออกจากแก้ว 4 และ 5 (จนกว่าจะเต็ม) แน่นอนนั่นหมายความว่าเราต้องตอบว่าแก้วหกและแก้วหกรั่วไหลออกมาเท่าไร แต่กลับกลายเป็นว่าไม่ยากเกินไป แก้วหกทะลักออกมามากแค่ไหน 5? ครึ่งหนึ่งของแก้วที่หกและสองทะลักออกมา แต่ลบด้วยลิตรที่อยู่ในแก้ว 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(); ควรมีที่ตั้งศูนย์กลางที่จับคู่ฝากข้อมูลกับ "ตัวป้อน" ของพวกเขา แต่ในการสัมภาษณ์ที่ความเร็วและความแม่นยำของการใช้งานมีความสำคัญมากกว่าที่ความเร็วของการดำเนินการและการบำรุงรักษา (เว้นแต่จะระบุไว้เป็นอย่างอื่น) โซลูชั่นนี้เป็นที่ต้องการ จากนั้นฉันจะเสนอให้ refactor รหัสให้ใกล้เคียงกับสิ่งที่ฉันพิจารณาถึงคุณภาพการผลิตและให้ผู้สัมภาษณ์ตัดสินใจ

หมายเหตุสุดท้าย: ฉันแน่ใจว่ารหัสของฉันมีการพิมพ์ผิดอยู่ที่ไหนสักแห่ง; ฉันจะพูดถึงสิ่งนั้นกับผู้สัมภาษณ์ด้วยและบอกว่าฉันจะรู้สึกมั่นใจมากขึ้นหลังจากทำการปรับโครงสร้างใหม่หรือทดสอบหน่วย


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)

จำนวนสูงสุด () คือเพื่อให้แน่ใจว่าเราจะไม่ได้รับจำนวนเงินที่ล้นเกิน

ใกล้เสร็จแล้ว! เราเลือกระบบพิกัดที่มี '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 ให้ใช้จำนวน _ ใน (ทั้งหมด, 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 จะเต็ม เมื่อเพิ่มลิตรที่หกลงไปมันจะเริ่มไหลลงใน 8 & 9 แต่ 7 และ 10 จะไม่ได้รับน้ำเพราะ 4 และ 6 ยังไม่ถึงขีดความสามารถ ดังนั้นฟังก์ชันทวินามจะไม่ทำนายค่าที่ถูกต้อง
Winston Ewert

3
-1 นี่เป็นสิ่งที่ผิด ระดับจะไม่ได้รับการเติมอย่างสม่ำเสมอ
dan_waterworth

คุณพูดถูกฉันไม่คิดเลย แต่หลังจากที่ฉันคิดเกี่ยวกับมันในขณะที่ฉันตระหนักว่าคุณถูกต้อง ไม่แน่ใจว่าจะปรับสูตรอย่างไรเพื่อพิจารณาสิ่งนี้
DeadMG
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.