Câu đố đài phun nước Champaign


30

Ly nước rỗng được sắp xếp theo thứ tự sau:

nhập mô tả hình ảnh ở đây

Khi bạn rót chất lỏng vào ly thứ 1 nếu nó đầy, thì chất lỏng thừa sẽ được đưa vào ly 2 và 3 với số lượng bằng nhau. Khi thủy tinh 2 đầy, chất lỏng dư sẽ được bay vào 4 và 5, v.v.

Cho N lít chất lỏng và dung tích tối đa của mỗi ly là 1 lít, hãy cho lượng chất lỏng có trong bất kỳ ly nào nếu bạn đổ N lít chất lỏng bằng cách rót vào ly bằng cách điền vào hàm getWaterInBucket(int N, int X)X là số thủy tinh. Vì vậy, ví dụ nếu tôi muốn có 4 lít lúc đầu và tôi muốn tìm nước trong ly 3 thì chức năng làgetWaterInBucket(4, 3)

Làm thế nào để tôi giải quyết điều này theo chương trình? Tôi đã cố gắng tìm một giải pháp toán học bằng tam giác Pascal. Điều này đã không làm việc. Tôi coi nó là một cái cây để tôi có thể thêm một tham số như thế này getWaterInBucket(BTree root, int N, int X)và sau đó thử một số giải pháp đệ quy cho từng cấp độ, nhưng các tham số không được phép trong vấn đề này. Có một cái gì đó rõ ràng, một số mẹo?


18
Tôi sẽ không muốn làm việc trong một công ty nơi các vấn đề của ban quản lý là về đài phun nước sâm banh ...
mouviciel

Bạn có thể rót vào ly khác ngoài ly 1 không? Nếu không, mỗi tầng sẽ có lượng nước bằng nhau trong mỗi ly. Như vậy, bạn sẽ có đầy đủ các tầng bất cứ khi nào bạn đổ 1, 3, 6, 10 ... lít. Nếu bạn rót 7 lít thì hàng thứ tư có 4 ly nên mỗi ly sẽ đầy 1/4. Tất cả các tầng trên nó sẽ đầy.
GlenPeterson

5
@GlenPeterson Từ cách tôi đọc nó, tôi không nghĩ họ sẽ lấp đầy như nhau. Có, 2 & 3 sẽ lấp đầy như nhau bởi vì họ chỉ có một thứ đổ vào chúng nhưng một khi chúng có đầy đủ 2 lần đổ vào 4/5 và 3 lần đổ vào 5/6, do đó 5 được điền vào gấp đôi số chuột 4/6 . Các cốc trung tâm luôn làm đầy nhanh hơn các cốc bên ngoài. đến thời điểm 4/6 là đầy đủ 8/9 là 25% đầy đủ và 7/10 vẫn trống.
Brad

1
Ngoài ra, điều này làm tôi nhớ đến Tam giác của Pascal ..
Brad

@mouviciel Haha GlenPeterson - Ly đầu tiên được rót luôn là ly 1. Người phỏng vấn cũng cho biết sử dụng thông tin này. Anh ta có vẻ bối rối hơn tôi về câu trả lời đúng cho vấn đề này.
Slartibartfast

Câu trả lời:


35

Bạn chỉ cần mô phỏng việc đổ, một cái gì đó như

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

Khi nó đứng, đây không phải là một cái cây. Bởi vì các loại kính khác nhau đổ vào cùng một loại kính, điều đó ngăn nó trở thành một cái cây.


16
+1 cho quan sát tuyệt vời rằng đây không phải là một cái cây.
Mihai Danila

2
Câu trả lời tốt. Trong một cuộc phỏng vấn, bạn nên nói rằng điều này có thể có vấn đề về khả năng mở rộng vì nó tính toán nội dung của tất cả các loại kính. Ngoài ra, bạn cần xử lý trường hợp nước đổ ra khỏi hàng kính dưới cùng. Và bạn muốn return glasses[N-1], bởi vì các số thủy tinh bắt đầu từ 1 thay vì 0.
Tom Panning

1
Tôi nghĩ rằng phần thử thách có thể là tìm ra các chỉ số của trẻ em trái và phải. Nếu bạn trình bày điều này, người phỏng vấn sẽ yêu cầu bạn thực hiện các chức năng đó. Có thể có một công thức rõ ràng.
James

Đó là một giải pháp thực sự thanh lịch. Cảm ơn. Tôi rất biết ơn nếu bạn có thể chỉnh sửa nó để thêm nhận xét vào các dòng mã để giải thích những gì mỗi bước biểu thị trong quá trình suy nghĩ. Ngoài ra số lượng kính không bị giới hạn ở 10. Nó có thể là bất cứ thứ gì
Slartibartfast

1
Làm thế nào để bạn tìm thấy kính trái và phải?
kiewic

7

Đây là cách tôi sẽ trả lời câu hỏi này trong một tình huống phỏng vấn (tôi chưa từng thấy câu hỏi này trước đây và tôi đã không nhìn vào các câu trả lời khác cho đến khi tôi có giải pháp của mình):

Đầu tiên, tôi đã cố gắng tìm ra nó (mà bạn gọi là "giải pháp toán học") và khi tôi đến kính 8 tôi nhận ra rằng nó sẽ khó khăn hơn dường như bởi vì kính 5 bắt đầu tràn ra trước kính 4. Lúc đó tôi quyết định đi theo con đường đệ quy (chỉ là một FYI, rất nhiều câu hỏi phỏng vấn lập trình đòi hỏi đệ quy hoặc quy nạp để giải quyết).

Suy nghĩ đệ quy, vấn đề trở nên dễ dàng hơn nhiều: Bao nhiêu nước trong ly 8? Một nửa số tiền đã đổ ra khỏi kính 4 và 5 (cho đến khi đầy). Tất nhiên, điều đó có nghĩa là chúng ta phải trả lời đã đổ ra bao nhiêu ly 4 và 5, nhưng hóa ra điều đó cũng không quá khó. Bao nhiêu đã tràn ra khỏi kính 5? Tuy nhiên, một nửa số tràn ra khỏi ly 2 và 3, trừ đi số lít còn lại trong ly 5.

Giải quyết hoàn toàn (và lộn xộn) này mang lại:

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

Tại thời điểm này (hoặc khi tôi viết bài này), tôi sẽ nói với người phỏng vấn rằng đây không phải là giải pháp lý tưởng trong sản xuất: có mã trùng lặp giữa howMuchSpilledOutOf()getWaterInBucket(); cần có một vị trí trung tâm để ánh xạ xô tới "người cho ăn" của họ. Nhưng trong một cuộc phỏng vấn, trong đó tốc độ và độ chính xác của việc thực hiện quan trọng hơn là tốc độ thực hiện và khả năng bảo trì (trừ khi có chỉ định khác), giải pháp này là thích hợp hơn. Sau đó, tôi sẽ đề nghị cấu trúc lại mã để gần hơn với những gì tôi cho là chất lượng sản xuất và để người phỏng vấn quyết định.

Lưu ý cuối cùng: Tôi chắc chắn mã của tôi có lỗi đánh máy ở đâu đó; Tôi cũng sẽ đề cập điều đó với người phỏng vấn và nói rằng tôi sẽ cảm thấy tin tưởng hơn vào nó sau khi tái cấu trúc nó hoặc kiểm tra đơn vị nó.


6
Giải pháp này được mã hóa cứng cho ví dụ. Thêm kính có nghĩa là thêm "vỏ" vào công tắc ... Tôi không nghĩ đó là một giải pháp tốt.
Luigi Massa Gallerano

2
@LuigiMassaGallerano - trong trường hợp này là một câu hỏi phỏng vấn; nó không phải là một giải pháp hoàn hảo Người phỏng vấn đang cố gắng hiểu rõ hơn về quá trình suy nghĩ của ứng viên. Và Tom đã chỉ ra điều đó this isn't the ideal solution.

1
Nó không thành thật. Tôi có thể đảm bảo với bạn rằng kịch bản này không có ý định mã hóa cứng. Nếu tôi hỏi một câu hỏi phỏng vấn và đưa ra một kịch bản trường hợp thử nghiệm mà người được phỏng vấn đưa ra một giải pháp mã hóa cứng, anh ta nên chuẩn bị để đưa ra một giải pháp chung hoặc có lẽ anh ta sẽ không vượt qua cuộc phỏng vấn.
Giàn khoan

5

Nghĩ về vấn đề này như một vấn đề về cây là một cá trích đỏ, nó thực sự là một biểu đồ có hướng. Nhưng hãy quên tất cả về điều đó.

Hãy nghĩ về một chiếc kính ở bất cứ đâu dưới cái trên cùng. Nó sẽ có một hoặc hai kính phía trên nó có thể tràn vào nó. Với sự lựa chọn phù hợp của hệ tọa độ (đừng lo lắng, hãy xem phần cuối), chúng ta có thể viết một hàm để lấy kính "cha mẹ" cho bất kỳ kính nào.

Bây giờ chúng ta có thể nghĩ về một thuật toán để lấy lượng chất lỏng đổ vào một ly, bất kể tràn ra từ ly đó. Tuy nhiên, câu trả lời là nhiều chất lỏng được đổ vào mỗi phụ huynh trừ đi lượng được lưu trữ trong mỗi ly bố mẹ, chia cho 2. Chỉ cần tính tổng cho tất cả các bậc cha mẹ. Viết cái này như một mảnh trăn của cơ thể của hàm lượng_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)

Tối đa () là để đảm bảo chúng tôi không nhận được số lượng tràn âm.

Chúng tôi sắp hoàn thành! Chúng tôi chọn hệ tọa độ có 'y' xuống trang, kính hàng thứ nhất là 0, hàng thứ hai là 1, v.v ... Các tọa độ 'x' có số 0 dưới kính hàng trên cùng và hàng thứ hai có tọa độ x là -1 và +1, hàng thứ ba -2, 0, +2, v.v. Điểm quan trọng là kính bên trái hoặc bên phải nhất ở cấp y sẽ có abs (x) = y.

Bao bọc tất cả những thứ đó thành python (2.x), chúng ta có:

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)

Vì vậy, để có được số tiền thực sự trong một ly tại p, hãy sử dụng lượng_in (tổng, p).

Không rõ ràng từ OP, nhưng bit về "bạn không thể thêm tham số" có thể có nghĩa là câu hỏi ban đầu phải được trả lời theo các số kính được hiển thị. Điều này được giải quyết bằng cách viết một hàm ánh xạ từ các số kính hiển thị sang hệ tọa độ bên trong được sử dụng ở trên. Đó là khó khăn, nhưng một giải pháp lặp hoặc toán học có thể được sử dụng. Một chức năng lặp dễ hiểu:

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)

Bây giờ chỉ cần viết lại hàm số lượng_in () ở trên để chấp nhận số thủy tinh:

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

Hấp dẫn.

Điều này có cách tiếp cận mô phỏng.

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

Những bản in (cho 6 lít):

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

có vẻ là đúng


-1

Đây là hàm nhị thức. Tỷ lệ nước giữa các ly cấp N có thể được phát hiện bằng cách sử dụng nCr cho mỗi ly trong cấp. Ngoài ra, tổng số kính trước cấp N là tổng từ 1 đến (N - 1), một công thức mà bạn sẽ có thể tìm thấy có sẵn khá dễ dàng. Do đó, với X, bạn sẽ có thể xác định mức của nó và sử dụng nCr để kiểm tra tỷ lệ của kính cho mức đó và do đó xác định lượng nước trong X là bao nhiêu, nếu có đủ lít để đi xuống X.

Thứ hai, ý tưởng của bạn về việc sử dụng BTree là tốt, chỉ là BTree là một biến nội bộ, không phải là một tham số bên ngoài.

IOW, nếu bạn bao quát toán học này trong giáo dục của bạn (ở đây tại Vương quốc Anh, nó được dạy trước khi học đại học) thì bạn sẽ có thể giải quyết vấn đề này mà không gặp quá nhiều vấn đề.


1
Tôi không nghĩ đó là hàm nhị thức. Nó đạt đến cấp độ thứ ba theo tỷ lệ 1,2,1 như hàm nhị thức sẽ đề xuất, nhưng kính giữa lấp đầy trước và mẫu bị phá vỡ sau đó.
Winston Ewert

Thời gian không phải là một phần của mô phỏng và nó sẽ không ảnh hưởng đến kết quả cuối cùng.
DeadMG

4
kể từ khi chất lỏng mô hình hóa của nó đầy và chảy ra khỏi kính, tôi phải duy trì thời gian đó hoàn toàn là một phần của mô phỏng. Ở mức 5 lít, 4 & 6 sẽ đầy một nửa và 5 sẽ đầy. Khi thêm lít thứ sáu, nó sẽ bắt đầu đổ vào 8 & 9, nhưng 7 & 10 sẽ không nhận được nước vì 4 & 6 vẫn chưa đạt công suất. Do đó, hàm nhị thức sẽ không dự đoán các giá trị chính xác.
Winston Ewert

3
-1, Điều này là sai. Cấp độ sẽ không được điền đầy đủ.
dan_waterworth

Bạn nói đúng, tôi đã không xem xét nó. Nhưng sau khi tôi nghĩ về nó một lúc, tôi nhận ra rằng bạn đã đúng. Không chắc chắn làm thế nào để điều chỉnh công thức để đưa điều này vào tài khoản.
DeadMG
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.