Viết chương trình tìm 100 số lớn nhất trong số 1 tỷ số


300

Gần đây tôi đã tham dự một cuộc phỏng vấn nơi tôi được yêu cầu "viết chương trình tìm 100 số lớn nhất trong số 1 tỷ số".

Tôi chỉ có thể đưa ra một giải pháp vũ lực đó là sắp xếp mảng theo độ phức tạp thời gian O (nlogn) và lấy 100 số cuối cùng.

Arrays.sort(array);

Người phỏng vấn đang tìm kiếm một sự phức tạp thời gian tốt hơn, tôi đã thử một vài giải pháp khác nhưng không trả lời được anh ta. Có một giải pháp phức tạp thời gian tốt hơn?


70
Có lẽ vấn đề là nó không phải là một câu hỏi sắp xếp , mà là một câu hỏi tìm kiếm .
geomagas

11
Là một lưu ý kỹ thuật, sắp xếp có thể không phải là cách tốt nhất để giải quyết vấn đề, nhưng tôi không nghĩ đó là sức mạnh vũ phu - tôi có thể nghĩ ra nhiều cách làm việc tồi tệ hơn.
Bernhard Barker

88
Tôi chỉ nghĩ về một phương pháp vũ phu thậm chí còn ngu ngốc hơn ... Tìm tất cả các kết hợp có thể có của 100 phần tử từ mảng phần tử 1 tỷ và xem những kết hợp nào trong số này có tổng lớn nhất.
Shashank

10
Lưu ý rằng tất cả các thuật toán xác định (và chính xác) là O(1)trong trường hợp này, bởi vì không có sự gia tăng kích thước. Người phỏng vấn nên hỏi "Làm thế nào để tìm m phần tử lớn nhất từ ​​một mảng n với n >> m?".
Bakuriu

Câu trả lời:


328

Bạn có thể giữ một hàng đợi ưu tiên trong số 100 số lớn nhất, lặp qua hàng tỷ số, bất cứ khi nào bạn gặp một số lớn hơn số nhỏ nhất trong hàng đợi (phần đầu của hàng đợi), hãy xóa phần đầu của hàng đợi và thêm số mới đến hàng đợi.

EDIT: như Dev đã lưu ý, với hàng đợi ưu tiên được triển khai với một đống, độ phức tạp của việc chèn vào hàng đợi làO(logN)

Trong trường hợp xấu nhất bạn nhận được cái nào tốt hơnbillionlog2(100)billionlog2(billion)

Nói chung, nếu bạn cần số K lớn nhất từ ​​một tập hợp số N, thì độ phức tạp O(NlogK)thay vì O(NlogN), điều này có thể rất đáng kể khi K rất nhỏ so với N.

EDIT2:

Thời gian dự kiến ​​của thuật toán này khá thú vị, vì trong mỗi lần lặp lại, việc chèn có thể xảy ra hoặc không. Xác suất của số thứ i được chèn vào hàng đợi là xác suất của một biến ngẫu nhiên lớn hơn ít nhất i-Kcác biến ngẫu nhiên từ cùng một phân phối (các số k đầu tiên được tự động thêm vào hàng đợi). Chúng ta có thể sử dụng số liệu thống kê đơn hàng (xem liên kết ) để tính xác suất này. Ví dụ: giả sử các số được chọn ngẫu nhiên thống nhất từ {0, 1}, giá trị dự kiến ​​của số thứ (iK) (trong số i số) là (i-k)/ivà khả năng biến ngẫu nhiên lớn hơn giá trị này là1-[(i-k)/i] = k/i .

Do đó, số lần chèn dự kiến ​​là:

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

Và thời gian chạy dự kiến ​​có thể được thể hiện là:

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

( kthời gian để tạo hàng đợi với các kyếu tố đầu tiên , sau đó n-kso sánh và số lần chèn dự kiến ​​như được mô tả ở trên, mỗi lần mất một log(k)/2thời gian trung bình )

Lưu ý rằng khi Nrất lớn so với K, biểu thức này gần hơn rất nhiều nso với NlogK. Điều này hơi trực quan, như trong trường hợp của câu hỏi, thậm chí sau 10000 lần lặp (rất nhỏ so với một tỷ), khả năng một số được chèn vào hàng đợi là rất nhỏ.


6
Nó thực sự chỉ là O (100) cho mỗi lần chèn.
MrSmith42

8
@RonTeller Bạn không thể tìm kiếm nhị phân một danh sách được liên kết một cách hiệu quả, đó là lý do tại sao hàng đợi ưu tiên thường được thực hiện với một đống. Thời gian chèn của bạn như được mô tả là O (n) chứ không phải O (logn). Bạn đã có nó ngay lần đầu tiên (hàng đợi theo thứ tự hoặc hàng ưu tiên) cho đến khi Skizz khiến bạn lần thứ hai tự đoán.
Dev

17
@ThomasJungblut tỷ cũng là một hằng số, vì vậy nếu đó là trường hợp O (1): P
Ron Teller

9
@RonTeller: bình thường loại câu hỏi lo ngại suy nghĩ như việc tìm kiếm 10 trang hàng đầu từ hàng tỷ kết quả tìm kiếm của Google, hoặc 50 từ thường gặp nhất đối với một đám mây từ, hoặc 10 bài hát phổ biến nhất trên MTV, vv Vì vậy, tôi tin rằng, trong trường hợp bình thường nó an toàn để xem xét k hằng sốnhỏ so với n. Mặc dù vậy, người ta phải luôn luôn ghi nhớ "hoàn cảnh bình thường" này.

5
Vì bạn có các mục 1G, lấy mẫu 1000 phần tử ngẫu nhiên và chọn 100 phần tử lớn nhất. Điều đó sẽ tránh các trường hợp suy biến (được sắp xếp, sắp xếp ngược, hầu hết được sắp xếp), giảm đáng kể số lần chèn.
ChuckCottrill

136

Nếu điều này được hỏi trong một cuộc phỏng vấn, tôi nghĩ người phỏng vấn có thể muốn xem quá trình giải quyết vấn đề của bạn, không chỉ là kiến ​​thức về thuật toán của bạn.

Mô tả khá chung chung nên có thể bạn có thể hỏi anh ta phạm vi hoặc ý nghĩa của những con số này để làm cho vấn đề rõ ràng. Làm điều này có thể gây ấn tượng với một người phỏng vấn. Ví dụ, nếu những con số này đại diện cho tuổi của mọi người trong một quốc gia (ví dụ: Trung Quốc), thì đó là một vấn đề dễ dàng hơn nhiều. Với giả định hợp lý rằng không ai còn sống trên 200 tuổi, bạn có thể sử dụng một mảng int có kích thước 200 (có thể là 201) để đếm số người có cùng độ tuổi chỉ trong một lần lặp. Ở đây chỉ số có nghĩa là tuổi. Sau đó, nó là một miếng bánh để tìm 100 số lớn nhất. Nhân tiện, thuật toán này được gọi là sắp xếp .

Dù sao, làm cho câu hỏi cụ thể hơn và rõ ràng hơn là tốt cho bạn trong một cuộc phỏng vấn.


26
Điểm rất tốt. Không ai khác đã hỏi hoặc chỉ ra bất cứ điều gì về việc phân phối những con số đó - nó có thể tạo ra tất cả sự khác biệt trong cách tiếp cận vấn đề.
NealB

13
Tôi muốn câu trả lời này đủ để mở rộng nó. Đọc các số một lần để nhận các giá trị tối thiểu / tối đa để bạn có thể giả sử phân phối. Sau đó, chọn một trong hai lựa chọn. Nếu phạm vi đủ nhỏ, hãy xây dựng một mảng nơi bạn chỉ cần kiểm tra số khi chúng xảy ra. Nếu phạm vi quá lớn, hãy sử dụng thuật toán heap đã sắp xếp được thảo luận ở trên .... Chỉ là một suy nghĩ.
Richard_G

2
Tôi đồng ý, đặt câu hỏi lại cho người phỏng vấn thực sự tạo ra nhiều sự khác biệt. Trong thực tế, một câu hỏi như bạn có bị giới hạn bởi sức mạnh tính toán hay không cũng có thể giúp bạn song song hóa giải pháp bằng cách sử dụng nhiều nút tính toán.
Sumit Nigam

1
@R_G Không cần phải đi qua toàn bộ danh sách. Đủ để lấy mẫu một phần nhỏ (ví dụ: một triệu) thành viên ngẫu nhiên trong danh sách để có được số liệu thống kê hữu ích.
Itamar

Đối với những người không nghĩ về giải pháp đó, tôi khuyên bạn nên đọc về cách sắp xếp en.wikipedia.org/wiki/Counting_sort . Đó thực sự là một câu hỏi phỏng vấn khá phổ biến: bạn có thể sắp xếp một mảng tốt hơn O (nlogn) không. Câu hỏi này chỉ là một phần mở rộng.
Maxime Chéramy

69

Bạn có thể lặp lại các số lấy O (n)

Bất cứ khi nào bạn tìm thấy một giá trị lớn hơn mức tối thiểu hiện tại, hãy thêm giá trị mới vào hàng đợi tròn với kích thước 100.

Giá trị tối thiểu của hàng đợi tròn đó là giá trị so sánh mới của bạn. Tiếp tục thêm vào hàng đợi đó. Nếu đầy, trích xuất tối thiểu từ hàng đợi.


3
Điều này không hoạt động. ví dụ: tìm top 2 của {1, 100, 2, 99} sẽ cho {100,1} là top 2.
Skizz

7
Bạn không thể đi xung quanh để giữ hàng đợi được sắp xếp. (nếu bạn không muốn tìm kiếm hàng đợi lỗ mỗi lần cho phần tử nhỏ nhất tiếp theo)
MrSmith42

3
@ MrSmith42 Sắp xếp một phần, như trong một đống, là đủ. Xem câu trả lời của Ron.
Christopher Creutzig

1
Có, tôi âm thầm cho rằng một hàng đợi trích xuất được thực hiện như một đống.
Regenschein

Thay vì hàng đợi tròn sử dụng đống tối thiểu kích thước 100, cái này sẽ có tối thiểu hàng trăm số ở trên cùng. Điều này sẽ chỉ mất O (log n) để chèn so với o (n) trong trường hợp xếp hàng
techExplorer

33

Tôi nhận ra rằng điều này được gắn thẻ 'thuật toán', nhưng sẽ đưa ra một số tùy chọn khác, vì có lẽ nó cũng nên được gắn thẻ 'phỏng vấn'.

Nguồn của 1 tỷ số là gì? Nếu đó là cơ sở dữ liệu thì 'chọn giá trị từ thứ tự bảng theo giá trị giới hạn desc 100' sẽ thực hiện công việc khá độc đáo - có thể có sự khác biệt về phương ngữ.

Đây là một lần, hoặc một cái gì đó sẽ được lặp lại? Nếu lặp đi lặp lại, bao lâu một lần? Nếu nó là một lần và dữ liệu nằm trong một tệp, thì 'cat srcfile | sắp xếp (tùy chọn khi cần) | head -100 'sẽ giúp bạn nhanh chóng thực hiện công việc hiệu quả mà bạn được trả tiền để làm trong khi máy tính xử lý công việc tầm thường này.

Nếu nó được lặp đi lặp lại, bạn sẽ khuyên chọn bất kỳ cách tiếp cận hợp lý nào để có câu trả lời ban đầu và lưu trữ / lưu trữ kết quả để bạn có thể liên tục có thể báo cáo top 100.

Cuối cùng, có sự cân nhắc này. Bạn đang tìm kiếm một công việc cấp đầu vào và phỏng vấn với một người quản lý táo bạo hoặc đồng nghiệp tương lai? Nếu vậy, sau đó bạn có thể đưa ra tất cả các cách tiếp cận mô tả các ưu và nhược điểm kỹ thuật tương đối. Nếu bạn đang tìm kiếm một công việc quản lý nhiều hơn, hãy tiếp cận nó như một người quản lý, quan tâm đến chi phí phát triển và bảo trì của giải pháp, và nói "cảm ơn rất nhiều" và rời đi nếu đó là người phỏng vấn muốn tập trung vào những chuyện vặt vãnh về CS . Anh ấy và bạn sẽ không có nhiều tiềm năng thăng tiến ở đó.

Chúc may mắn hơn trong cuộc phỏng vấn tiếp theo.


2
Câu trả lời đặc biệt. Mọi người khác đã tập trung vào khía cạnh kỹ thuật của câu hỏi, trong khi câu trả lời này đã giải quyết phần xã hội kinh doanh của nó.
vbocan

2
Tôi không bao giờ tưởng tượng bạn có thể nói cảm ơn và rời khỏi một cuộc phỏng vấn và không chờ đợi nó kết thúc. Cảm ơn đã mở mang đầu óc tôi.
UrsulRosu

1
Tại sao chúng ta không thể tạo ra một đống tỷ phần tử và trích xuất 100 phần tử lớn nhất. Cách này có giá = O (tỷ) + 100 * O (log (tỷ)) ??
Mohit Shah

17

Phản ứng ngay lập tức của tôi cho việc này sẽ là sử dụng một đống, nhưng có cách sử dụng QuickSelect mà không cần giữ tất cả các giá trị đầu vào bất cứ lúc nào.

Tạo một mảng có kích thước 200 và điền vào nó với 200 giá trị đầu vào đầu tiên. Chạy QuickSelect và loại bỏ 100 thấp, để lại cho bạn 100 địa điểm miễn phí. Đọc trong 100 giá trị đầu vào tiếp theo và chạy lại QuickSelect. Tiếp tục cho đến khi bạn đã chạy mặc dù toàn bộ đầu vào theo lô 100.

Cuối cùng, bạn có 100 giá trị hàng đầu. Đối với N giá trị, bạn đã chạy QuickSelect khoảng N / 100 lần. Mỗi Quickselect có giá khoảng 200 lần một số hằng số, vì vậy tổng chi phí là 2N một số hằng số. Điều này có vẻ tuyến tính trong kích thước của đầu vào đối với tôi, bất kể kích thước tham số mà tôi khó có thể là 100 trong giải thích này.


10
Bạn có thể thêm một tối ưu hóa nhỏ nhưng có thể quan trọng: Sau khi chạy QuickSelect để phân vùng mảng kích thước 200, tối thiểu trong số 100 phần tử hàng đầu được biết đến. Sau đó, khi lặp lại trên toàn bộ tập dữ liệu, chỉ điền vào 100 giá trị thấp hơn nếu giá trị hiện tại lớn hơn mức tối thiểu hiện tại. Một triển khai đơn giản của thuật toán này trong C ++ ngang bằng với libstdc ++ partial_sortchạy trực tiếp trên bộ dữ liệu 200 triệu 32 bit int(được tạo thông qua MT19937, được phân phối đồng đều).
dyp

1
Ý tưởng tốt - không ảnh hưởng đến phân tích trường hợp xấu nhất nhưng có vẻ đáng làm.
mcdowella

@mcdowella Rất đáng để thử và tôi sẽ làm được, cảm ơn!
userx

8
Đây chính xác là những gì Guava Ordering.greatestOf(Iterable, int) làm. Đó là thời gian hoàn toàn tuyến tính và một lần vượt qua, và đó là một thuật toán siêu dễ thương. FWIW, chúng tôi cũng có một số điểm chuẩn thực tế: các yếu tố không đổi của nó chậm hơn so với hàng đợi ưu tiên truyền thống trong trường hợp trung bình, nhưng việc triển khai này có khả năng chống lại đầu vào "trường hợp xấu nhất" (ví dụ như đầu vào tăng dần).
Louis Wasserman

15

Bạn có thể sử dụng thuật toán Chọn nhanh để tìm số tại chỉ mục (theo thứ tự) [tỷ-101] và sau đó lặp qua các số và để tìm các số lớn hơn từ số đó.

array={...the billion numbers...} 
result[100];

pivot=QuickSelect(array,billion-101);//O(N)

for(i=0;i<billion;i++)//O(N)
   if(array[i]>=pivot)
      result.add(array[i]);

Thuật toán này Thời gian là: 2 XO (N) = O (N) (Hiệu suất trường hợp trung bình)

Tùy chọn thứ hai như Thomas Jungblut gợi ý là:

Sử dụng đống xây dựng heap MAX sẽ lấy O (N), sau đó 100 số tối đa hàng đầu sẽ nằm trong đầu Heap, tất cả những gì bạn cần là lấy chúng ra khỏi heap (100 XO (Nhật ký (N)).

Thuật toán này Thời gian là: O (N) + 100 XO (Nhật ký (N)) = O (N)


8
Bạn đang làm việc thông qua toàn bộ danh sách ba lần. 1 sinh học. số nguyên có dung lượng khoảng 4gb, bạn sẽ làm gì nếu không thể lắp chúng vào bộ nhớ? quickselect là sự lựa chọn tồi tệ nhất có thể trong trường hợp này. Lặp lại một lần và giữ một đống trong số 100 mục hàng đầu là IMHO giải pháp hoạt động tốt nhất trong O (n) (lưu ý rằng bạn có thể cắt bỏ O (log n) của heap chèn vì n trong heap là 100 = hằng số = rất nhỏ ).
Thomas Jungblut

3
Mặc dù vẫn còn O(N), thực hiện hai QuickSelect và quét tuyến tính khác là quá nhiều chi phí hơn mức cần thiết.
Kevin

Đây là mã PSEUDO, tất cả các giải pháp ở đây sẽ mất nhiều thời gian hơn (O (NLOG (N) hoặc 100 * O (N))
One Man crew

1
100*O(N)(nếu đó là cú pháp hợp lệ) = O(100*N)= O(N)(phải thừa nhận 100 có thể là biến, nếu vậy, điều này không hoàn toàn đúng). Ồ, và Quickselect có hiệu suất trong trường hợp xấu nhất là O (N ^ 2) (ouch). Và nếu nó không vừa với bộ nhớ, bạn sẽ tải lại dữ liệu từ đĩa hai lần, điều này tệ hơn rất nhiều so với một lần (đây là nút cổ chai).
Bernhard Barker

Có một vấn đề là đây là thời gian chạy dự kiến, và không phải là trường hợp xấu nhất, nhưng bằng cách sử dụng chiến lược lựa chọn trục chính (ví dụ chọn ngẫu nhiên 21 yếu tố và chọn trung vị của 21 đó làm trục), thì số lượng so sánh có thể là được đảm bảo với xác suất cao nhất là (2 + c) n cho hằng số nhỏ tùy ý c.
Một người đàn ông phi hành đoàn

10

Mặc dù giải pháp quickselect khác đã bị hạ cấp, nhưng thực tế là quickselect sẽ tìm ra giải pháp nhanh hơn so với sử dụng hàng đợi có kích thước 100. Quickselect có thời gian chạy dự kiến ​​là 2n + o (n), về mặt so sánh. Một cách thực hiện rất đơn giản sẽ là

array = input array of length n
r = Quickselect(array,n-100)
result = array of length 100
for(i = 1 to n)
  if(array[i]>r)
     add array[i] to result

Điều này sẽ mất trung bình 3n + o (n) so sánh. Hơn nữa, nó có thể được thực hiện hiệu quả hơn bằng cách sử dụng quickselect sẽ để lại 100 mục lớn nhất trong mảng ở 100 vị trí ngoài cùng bên phải. Vì vậy, trên thực tế, thời gian chạy có thể được cải thiện thành 2n + o (n).

Có một vấn đề là đây là thời gian chạy dự kiến, và không phải là trường hợp xấu nhất, nhưng bằng cách sử dụng chiến lược lựa chọn trục chính (ví dụ chọn ngẫu nhiên 21 yếu tố và chọn trung vị của 21 đó làm trục), thì số lượng so sánh có thể là được đảm bảo với xác suất cao nhất là (2 + c) n cho hằng số nhỏ tùy ý c.

Trên thực tế, bằng cách sử dụng chiến lược lấy mẫu được tối ưu hóa (ví dụ: các phần tử sqrt (n) mẫu một cách ngẫu nhiên và chọn phân vị thứ 99), thời gian chạy có thể được giảm xuống (1 + c) n + o (n) cho c nhỏ tùy ý (giả sử rằng K, số phần tử được chọn là o (n)).

Mặt khác, sử dụng hàng đợi có kích thước 100 sẽ yêu cầu so sánh O (log (100) n) và cơ sở log 2 của 100 xấp xỉ bằng 6,6.

Nếu chúng ta nghĩ về vấn đề này theo nghĩa trừu tượng hơn là chọn các phần tử K lớn nhất từ ​​một mảng có kích thước N, trong đó K = o (N) nhưng cả K và N đều chuyển sang vô cùng, thì thời gian chạy của phiên bản quickselect sẽ là O (N) và phiên bản hàng đợi sẽ là O (N log K), vì vậy theo nghĩa này, quickselect cũng vượt trội về mặt tiệm cận.

Trong các bình luận, nó đã được đề cập rằng giải pháp hàng đợi sẽ chạy trong thời gian dự kiến ​​N + K log N trên một đầu vào ngẫu nhiên. Tất nhiên, giả định đầu vào ngẫu nhiên không bao giờ hợp lệ trừ khi câu hỏi nêu rõ. Giải pháp hàng đợi có thể được thực hiện để duyệt qua mảng theo thứ tự ngẫu nhiên, nhưng điều này sẽ phát sinh thêm chi phí của các cuộc gọi N đến một trình tạo số ngẫu nhiên cũng như hoán vị toàn bộ mảng đầu vào hoặc phân bổ một mảng mới có độ dài N chứa chỉ số ngẫu nhiên.

Nếu sự cố không cho phép bạn di chuyển xung quanh các phần tử trong mảng ban đầu và chi phí phân bổ bộ nhớ cao nên việc sao chép mảng không phải là một tùy chọn, đó là một vấn đề khác. Nhưng nghiêm ngặt về thời gian chạy, đây là giải pháp tốt nhất.


4
Đoạn cuối cùng của bạn là điểm mấu chốt: với một tỷ số, việc giữ tất cả dữ liệu trong bộ nhớ hoặc trao đổi các yếu tố xung quanh là không khả thi. (Ít nhất đó là cách tôi sẽ giải thích vấn đề, cho rằng đó là một câu hỏi phỏng vấn.)
Ted Hopp

14
Trong bất kỳ câu hỏi thuật toán, nếu đọc dữ liệu là một vấn đề, nó phải được đề cập trong câu hỏi. Câu hỏi nêu rõ "đưa ra một mảng" chứ không phải "đưa ra một mảng trên đĩa không phù hợp với bộ nhớ và không thể được xử lý theo mô hình von neuman, là tiêu chuẩn trong phân tích thuật toán". Những ngày này, bạn có thể nhận được một máy tính xách tay với 8gigs ram. Tôi không chắc ý tưởng giữ một tỷ số trong bộ nhớ là không khả thi đến từ đâu. Tôi có vài tỷ số trong bộ nhớ trên máy trạm của mình ngay bây giờ.
mrip

Thời gian chạy tệ nhất của FYI của quickselect là O (n ^ 2) (xem en.wikipedia.org/wiki/Quickselect ) và nó cũng sửa đổi thứ tự các phần tử trong mảng đầu vào. Có thể có một giải pháp O (n) trong trường hợp xấu nhất, với hằng số rất lớn ( en.wikipedia.org/wiki/Median_of_medians ).
pts

Trường hợp xấu nhất của quickselect là không thể xảy ra theo cấp số nhân, điều đó có nghĩa là vì mục đích thực tế, điều này không liên quan. Thật dễ dàng để sửa đổi quickselect để với xác suất cao, số lượng so sánh là (2 + c) n + o (n) cho c nhỏ tùy ý.
mrip

"Thực tế là quickselect sẽ tìm ra giải pháp nhanh hơn so với sử dụng hàng đợi có kích thước 100" - Không. Giải pháp heap lấy khoảng so sánh N + Klog (N) so với trung bình 2N cho quickselect và 2,95 cho Median of Median. Rõ ràng là nhanh hơn đối với K. đã cho
Neil G

5

lấy 100 số đầu tiên của tỷ và sắp xếp chúng. bây giờ chỉ cần lặp qua tỷ, nếu số nguồn cao hơn số nhỏ nhất là 100, hãy chèn theo thứ tự sắp xếp. Những gì bạn kết thúc là một cái gì đó gần với O (n) hơn kích thước của tập hợp.


3
Rất tiếc, không thấy câu trả lời chi tiết hơn của tôi.
Samuel Thurston

Lấy 500 số đầu tiên hoặc lâu hơn và chỉ dừng lại để sắp xếp (và ném ra mức thấp 400) khi danh sách đầy. (Và không cần phải nói rằng sau đó bạn chỉ thêm vào danh sách nếu số mới> thấp nhất trong 100 được chọn.)
Hot Licks

4

Hai lựa chọn:

(1) Heap (ưu tiên hàng đầu)

Duy trì một đống nhỏ với kích thước 100. Di chuyển mảng. Khi phần tử nhỏ hơn phần tử đầu tiên trong heap, thay thế nó.

InSERT ELEMENT INTO HEAP: O(log100)
compare the first element: O(1)
There are n elements in the array, so the total would be O(nlog100), which is O(n)

(2) Mô hình thu nhỏ bản đồ.

Điều này rất giống với ví dụ đếm từ trong hadoop. Bản đồ công việc: đếm tần số hoặc thời gian của mọi yếu tố xuất hiện. Giảm: Nhận phần tử K hàng đầu.

Thông thường, tôi sẽ cung cấp cho nhà tuyển dụng hai câu trả lời. Cung cấp cho họ bất cứ điều gì họ thích. Tất nhiên, bản đồ giảm mã hóa sẽ là lao động - một số vì bạn phải biết mọi thông số chính xác. Không có hại để thực hành nó. Chúc may mắn.


+1 cho MapReduce, tôi không thể tin rằng bạn là người duy nhất nhắc đến Hadoop cho một tỷ số. Nếu người phỏng vấn yêu cầu số 1 tỷ thì sao? Bạn xứng đáng được nhiều phiếu hơn theo ý kiến ​​của tôi.
Silviu Burcea

@Silviu Burcea Cảm ơn rất nhiều. Tôi cũng coi trọng MapReduce. :)
Chris Su

Mặc dù kích thước 100 là không đổi trong ví dụ này, bạn thực sự nên khái quát nó thành một biến riêng biệt. k. Vì 100 là hằng số là 1 tỷ, vậy tại sao bạn lại cho kích thước của tập hợp số lớn một biến kích thước là n chứ không phải cho tập hợp số nhỏ hơn? Thực sự độ phức tạp của bạn phải là O (nlogk) không phải là O (n).
Tom nghe

1
Nhưng quan điểm của tôi là nếu bạn chỉ trả lời câu hỏi, 1 tỷ cũng được cố định trong câu hỏi vậy tại sao lại khái quát 1 tỷ đến n chứ không phải 100 đến k. Theo logic của bạn, độ phức tạp thực sự phải là O (1) vì cả 1 tỷ và 100 đều được cố định trong câu hỏi này.
Tom Nghe

1
@TomHeard Được rồi. O (nlogk) Chỉ có một yếu tố sẽ ảnh hưởng đến kết quả. Điều này có nghĩa, nếu n ngày càng lớn hơn, "mức kết quả" sẽ tăng tuyến tính. Hoặc chúng ta có thể nói, ngay cả khi đưa ra con số nghìn tỷ, tôi vẫn có thể nhận được 100 số lớn nhất. Tuy nhiên, bạn không thể nói: Với việc tăng n, k tăng lên do đó k sẽ ảnh hưởng đến kết quả. Đó là lý do tại sao tôi sử dụng O (nlogk) chứ không phải O (nlogn)
Chris Su

4

Một giải pháp rất dễ dàng là lặp đi lặp lại qua mảng 100 lần. Đó là O(n).

Mỗi lần bạn rút số lớn nhất (và thay đổi giá trị của nó thành giá trị tối thiểu, để bạn không nhìn thấy nó trong lần lặp tiếp theo hoặc theo dõi các chỉ mục của các câu trả lời trước đó (bằng cách theo dõi các chỉ mục mà mảng ban đầu có thể có bội số của cùng một số)). Sau 100 lần lặp, bạn có 100 số lớn nhất.


1
Hai nhược điểm - (1) Bạn đang phá hủy đầu vào trong quy trình - tốt nhất nên tránh. (2) Bạn đang đi qua mảng nhiều lần - nếu mảng được lưu trên đĩa và không vừa với bộ nhớ, điều này có thể dễ dàng chậm hơn gần 100 lần so với câu trả lời được chấp nhận. (Vâng, cả hai đều là O (n), nhưng vẫn)
Bernhard Barker

Cuộc gọi tốt @Dukeling, tôi đã thêm từ ngữ bổ sung về cách tránh thay đổi đầu vào ban đầu bằng cách theo dõi các chỉ số trả lời trước đó. Mà vẫn sẽ khá dễ dàng để mã.
James Oravec

Một ví dụ tuyệt vời về giải pháp O (n) chậm hơn nhiều so với O (n log n). log2 (1 tỷ) chỉ 30 ...
gnasher729

@ gnasher729 Hằng số ẩn trong O (n log n) lớn đến mức nào?
phép lạ173

1

Lấy cảm hứng từ câu trả lời của @ron, đây là chương trình C barebones để làm những gì bạn muốn.

#include <stdlib.h>
#include <stdio.h>

#define TOTAL_NUMBERS 1000000000
#define N_TOP_NUMBERS 100

int 
compare_function(const void *first, const void *second)
{
    int a = *((int *) first);
    int b = *((int *) second);
    if (a > b){
        return 1;
    }
    if (a < b){
        return -1;
    }
    return 0;
}

int 
main(int argc, char ** argv)
{
    if(argc != 2){
        printf("please supply a path to a binary file containing 1000000000"
               "integers of this machine's wordlength and endianness\n");
        exit(1);
    }
    FILE * f = fopen(argv[1], "r");
    if(!f){
        exit(1);
    }
    int top100[N_TOP_NUMBERS] = {0};
    int sorts = 0;
    for (int i = 0; i < TOTAL_NUMBERS; i++){
        int number;
        int ok;
        ok = fread(&number, sizeof(int), 1, f);
        if(!ok){
            printf("not enough numbers!\n");
            break;
        }
        if(number > top100[0]){
            sorts++;
            top100[0] = number;
            qsort(top100, N_TOP_NUMBERS, sizeof(int), compare_function);
        }

    }
    printf("%d sorts made\n"
    "the top 100 integers in %s are:\n",
    sorts, argv[1] );
    for (int i = 0; i < N_TOP_NUMBERS; i++){
        printf("%d\n", top100[i]);
    }
    fclose(f);
    exit(0);
}

Trên máy của tôi (lõi i3 với ổ SSD nhanh) phải mất 25 giây và 1724 loại. Tôi đã tạo một tệp nhị phân vớidd if=/dev/urandom/ count=1000000000 bs=1 cho lần chạy này.

Rõ ràng, có những vấn đề về hiệu năng khi chỉ đọc 4 byte mỗi lần - từ đĩa, nhưng đây là ví dụ. Về mặt tích cực, rất ít bộ nhớ là cần thiết.


1

Giải pháp đơn giản nhất là quét hàng tỷ số mảng lớn và giữ 100 giá trị lớn nhất được tìm thấy cho đến nay trong một bộ đệm mảng nhỏ mà không cần sắp xếp và ghi nhớ giá trị nhỏ nhất của bộ đệm này. Đầu tiên tôi nghĩ phương pháp này được đề xuất bởi fordpreinf nhưng trong một bình luận, ông nói rằng ông giả định cấu trúc dữ liệu 100 số đang được thực hiện như một đống. Bất cứ khi nào một số mới được tìm thấy lớn hơn thì mức tối thiểu trong bộ đệm sẽ bị ghi đè bởi giá trị mới được tìm thấy và bộ đệm được tìm kiếm lại mức tối thiểu hiện tại một lần nữa. Nếu các số trong mảng tỷ được phân phối ngẫu nhiên hầu hết thời gian, giá trị từ mảng lớn được so sánh với mức tối thiểu của mảng nhỏ và bị loại bỏ. Chỉ với một phần rất nhỏ của số, giá trị phải được chèn vào mảng nhỏ. Vì vậy, sự khác biệt của việc thao tác cấu trúc dữ liệu giữ các số nhỏ có thể bị bỏ qua. Đối với một số lượng nhỏ các yếu tố, thật khó để xác định xem việc sử dụng hàng đợi ưu tiên có thực sự nhanh hơn so với sử dụng phương pháp ngây thơ của tôi hay không.

Tôi muốn ước tính số lượng chèn trong bộ đệm mảng phần tử nhỏ 100 khi mảng phần tử 10 ^ 9 được quét. Chương trình quét 1000 phần tử đầu tiên của mảng lớn này và phải chèn tối đa 1000 phần tử trong bộ đệm. Bộ đệm chứa 100 phần tử của 1000 phần tử được quét, đó là 0,1 phần tử được quét. Vì vậy, chúng tôi giả định rằng xác suất mà một giá trị từ mảng lớn lớn hơn mức tối thiểu hiện tại của bộ đệm là khoảng 0,1 Một phần tử như vậy phải được chèn vào bộ đệm. Bây giờ chương trình quét 10 ^ 4 phần tử tiếp theo từ mảng lớn. Bởi vì mức tối thiểu của bộ đệm sẽ tăng lên mỗi khi một phần tử mới được chèn vào. Chúng tôi ước tính tỷ lệ các phần tử lớn hơn mức tối thiểu hiện tại của chúng tôi là khoảng 0,1 và do đó, có 0,1 * 10 ^ 4 = 1000 phần tử để chèn. Trên thực tế, số lượng phần tử dự kiến ​​được chèn vào bộ đệm sẽ nhỏ hơn. Sau khi quét 10 ^ 4 phần tử này, các phần trong các bộ đệm sẽ có khoảng 0,01 phần tử được quét cho đến nay. Vì vậy, khi quét 10 ^ 5 số tiếp theo, chúng tôi giả sử rằng không quá 0,01 * 10 ^ 5 = 1000 sẽ được chèn vào bộ đệm. Tiếp tục lập luận này, chúng tôi đã chèn khoảng 7000 giá trị sau khi quét 1000 + 10 ^ 4 + 10 ^ 5 + ... + 10 ^ 9 ~ 10 ^ 9 phần tử của mảng lớn. Vì vậy, khi quét một mảng có 10 ^ 9 phần tử có kích thước ngẫu nhiên, chúng tôi mong đợi không quá 10 ^ 4 (= 7000 làm tròn) các phần chèn vào bộ đệm. Sau mỗi lần chèn vào bộ đệm, mức tối thiểu mới phải được tìm thấy. Nếu bộ đệm là một mảng đơn giản, chúng ta cần 100 so sánh để tìm mức tối thiểu mới. Nếu bộ đệm là một cấu trúc dữ liệu khác (như một đống), chúng ta cần ít nhất 1 so sánh để tìm mức tối thiểu. Để so sánh các yếu tố của mảng lớn, chúng ta cần 10 ^ 9 so sánh. Vì vậy, tất cả chúng ta cần khoảng 10 ^ 9 + 100 * 10 ^ 4 = 1.001 * 10 ^ 9 so sánh khi sử dụng một mảng làm bộ đệm và ít nhất 1.000 * 10 ^ 9 so sánh khi sử dụng một loại cấu trúc dữ liệu khác (như một đống) . Vì vậy, sử dụng một đống chỉ mang lại mức tăng 0,1% nếu hiệu suất được xác định bởi số lượng so sánh. Nhưng sự khác biệt về thời gian thực hiện giữa việc chèn một phần tử trong heap phần tử 100 và thay thế một phần tử trong mảng 100 phần tử và tìm mức tối thiểu mới của nó là gì? 000 * 10 ^ 9 so sánh khi sử dụng một loại cấu trúc dữ liệu khác (như một đống). Vì vậy, sử dụng một đống chỉ mang lại mức tăng 0,1% nếu hiệu suất được xác định bởi số lượng so sánh. Nhưng sự khác biệt về thời gian thực hiện giữa việc chèn một phần tử trong heap phần tử 100 và thay thế một phần tử trong mảng 100 phần tử và tìm mức tối thiểu mới của nó là gì? 000 * 10 ^ 9 so sánh khi sử dụng một loại cấu trúc dữ liệu khác (như một đống). Vì vậy, sử dụng một đống chỉ mang lại mức tăng 0,1% nếu hiệu suất được xác định bởi số lượng so sánh. Nhưng sự khác biệt về thời gian thực hiện giữa việc chèn một phần tử trong heap phần tử 100 và thay thế một phần tử trong mảng 100 phần tử và tìm mức tối thiểu mới của nó là gì?

  • Ở cấp độ lý thuyết: Cần bao nhiêu so sánh để chèn vào một đống. Tôi biết đó là O (log (n)) nhưng hệ số không đổi lớn đến mức nào? Tôi

  • Ở cấp độ máy: Tác động của bộ nhớ đệm và dự đoán nhánh đến thời gian thực hiện của một heap insert và tìm kiếm tuyến tính trong một mảng.

  • Ở cấp độ thực hiện: Chi phí bổ sung nào được ẩn trong cấu trúc dữ liệu heap do thư viện hoặc trình biên dịch cung cấp?

Tôi nghĩ rằng đây là một số câu hỏi phải được trả lời trước khi người ta có thể cố gắng ước tính sự khác biệt thực sự giữa hiệu suất của một đống 100 phần tử hoặc một mảng 100 phần tử. Vì vậy, sẽ rất hợp lý khi thực hiện một thử nghiệm và đo lường hiệu suất thực sự.


1
Đó là những gì một đống làm.
Neil G

@Neil G: "cái đó" là gì?
phép lạ173

1
Đỉnh của heap là phần tử tối thiểu trong heap và các phần tử mới bị từ chối với một so sánh.
Neil G

1
Tôi hiểu những gì bạn đang nói, nhưng ngay cả khi bạn đi theo số lượng so sánh tuyệt đối thay vì số so sánh không có triệu chứng, mảng vẫn chậm hơn nhiều vì thời gian để "chèn phần tử mới, loại bỏ tối thiểu cũ và tìm mức tối thiểu mới" là 100 chứ không phải khoảng 7.
Neil G

1
Được rồi, nhưng ước tính của bạn là rất bùng binh. Bạn có thể tính trực tiếp số lượng chèn dự kiến ​​là k (digamma (n) - digamma (k)), ít hơn klog (n). Trong mọi trường hợp, cả heap và giải pháp mảng chỉ dành một so sánh để loại bỏ một phần tử. Sự khác biệt duy nhất là số lượng so sánh cho một phần tử được chèn là 100 cho giải pháp của bạn so với tối đa 14 cho heap (mặc dù trường hợp trung bình có lẽ ít hơn nhiều.)
Neil G

1
 Although in this question we should search for top 100 numbers, I will 
 generalize things and write x. Still, I will treat x as constant value.

Thuật toán Các phần tử x lớn nhất từ ​​n:

Tôi sẽ gọi LIST giá trị trả về . Đó là một tập hợp các phần tử x (theo ý kiến ​​của tôi nên được liên kết danh sách)

  • Các phần tử x đầu tiên được lấy từ nhóm "khi chúng đến" và được sắp xếp trong LIST (điều này được thực hiện trong thời gian không đổi vì x được coi là hằng số - thời gian O (x log (x)))
  • Đối với mọi phần tử tiếp theo, chúng tôi kiểm tra xem phần tử đó có lớn hơn phần tử nhỏ nhất trong LIST không và liệu chúng tôi có bật phần tử hiện tại nhỏ nhất và chèn vào LIST không. Vì đó là danh sách được sắp xếp, mọi phần tử sẽ tìm thấy vị trí của nó trong thời gian logarit (tìm kiếm nhị phân) và vì nó được sắp xếp danh sách nên việc chèn danh sách không phải là vấn đề. Mỗi bước cũng được thực hiện trong thời gian không đổi (O (log (x))).

Vì vậy, trường hợp xấu nhất là gì?

x log (x) + (nx) (log (x) +1) = nlog (x) + n - x

Vì vậy, đó là thời gian O (n) cho trường hợp xấu nhất. +1 là kiểm tra nếu số lớn hơn số nhỏ nhất trong LIST. Thời gian dự kiến ​​cho trường hợp trung bình sẽ phụ thuộc vào phân phối toán học của n phần tử đó.

Cải tiến có thể

Thuật toán này có thể được cải thiện một chút cho trường hợp xấu nhất nhưng IMHO (tôi không thể chứng minh yêu cầu này) sẽ làm giảm hành vi trung bình. Hành vi tiệm cận sẽ giống nhau.

Cải tiến trong thuật toán này sẽ là chúng tôi sẽ không kiểm tra xem phần tử có lớn hơn nhỏ nhất không. Đối với mỗi phần tử, chúng tôi sẽ cố gắng chèn nó và nếu nó nhỏ hơn nhỏ nhất, chúng tôi sẽ bỏ qua nó. Mặc dù điều đó nghe có vẻ vô lý nếu chúng ta chỉ xem xét trường hợp xấu nhất chúng ta sẽ có

x log (x) + (nx) log (x) = nlog (x)

hoạt động.

Đối với trường hợp sử dụng này, tôi không thấy bất kỳ cải tiến nào nữa. Tuy nhiên, bạn phải tự hỏi - điều gì sẽ xảy ra nếu tôi phải làm điều này nhiều hơn log (n) lần và cho các x-es khác nhau? Rõ ràng chúng ta sẽ sắp xếp mảng đó trong O (n log (n)) và lấy phần tử x của chúng ta bất cứ khi nào chúng ta cần chúng.


1

Câu hỏi này sẽ được trả lời với độ phức tạp của log N (100) (thay vì N log N) chỉ với một dòng mã C ++.

 std::vector<int> myvector = ...; // Define your 1 billion numbers. 
                                 // Assumed integer just for concreteness 
 std::partial_sort (myvector.begin(), myvector.begin()+100, myvector.end());

Câu trả lời cuối cùng sẽ là một vectơ trong đó 100 phần tử đầu tiên được đảm bảo là 100 số lớn nhất trong mảng của bạn trong khi các phần tử còn lại không được sắp xếp

C ++ STL (thư viện chuẩn) khá tiện dụng cho loại vấn đề này.

Lưu ý: Tôi không nói rằng đây là giải pháp tối ưu, nhưng nó sẽ cứu cuộc phỏng vấn của bạn.


1

Giải pháp đơn giản là sử dụng hàng đợi ưu tiên, thêm 100 số đầu tiên vào hàng đợi và theo dõi số nhỏ nhất trong hàng đợi, sau đó lặp qua hàng tỷ số khác và mỗi lần chúng ta tìm thấy một số lớn hơn số lớn nhất trong hàng đợi ưu tiên, chúng tôi xóa số nhỏ nhất, thêm số mới và theo dõi lại số nhỏ nhất trong hàng.

Nếu các số theo thứ tự ngẫu nhiên, điều này sẽ hoạt động tốt bởi vì khi chúng ta lặp qua một tỷ số ngẫu nhiên, sẽ rất hiếm khi số tiếp theo nằm trong số 100 lớn nhất cho đến nay. Nhưng những con số có thể không ngẫu nhiên. Nếu mảng đã được sắp xếp theo thứ tự tăng dần thì chúng ta sẽ luôn luôn chèn một phần tử vào hàng ưu tiên.

Vì vậy, chúng tôi chọn nói 100.000 số ngẫu nhiên từ mảng đầu tiên. Để tránh truy cập ngẫu nhiên có thể chậm, chúng tôi thêm 400 nhóm ngẫu nhiên gồm 250 số liên tiếp. Với lựa chọn ngẫu nhiên đó, chúng ta có thể khá chắc chắn rằng rất ít số còn lại nằm trong top trăm, vì vậy thời gian thực hiện sẽ rất gần với một vòng lặp đơn giản so sánh một tỷ số với một giá trị tối đa.


1

Tìm kiếm top 100 trong số một tỷ số được thực hiện tốt nhất bằng cách sử dụng min-heap 100 phần tử.

Đầu tiên là heap min với 100 số đầu tiên gặp phải. min-heap sẽ lưu trữ số nhỏ nhất trong số 100 số đầu tiên ở gốc (trên cùng).

Bây giờ khi bạn đi dọc theo các con số còn lại, chỉ so sánh chúng với gốc (nhỏ nhất trong số 100).

Nếu số mới gặp phải lớn hơn root của min-heap, hãy thay thế root bằng số đó nếu không thì bỏ qua nó.

Là một phần của việc chèn số mới trong min-heap, số nhỏ nhất trong heap sẽ lên đến đỉnh (root).

Khi chúng tôi đã trải qua tất cả các số, chúng tôi sẽ có 100 số lớn nhất trong heap.


0

Tôi đã viết lên một giải pháp đơn giản trong Python trong trường hợp có ai quan tâm. Nó sử dụng bisectmô-đun và một danh sách trả lại tạm thời mà nó được sắp xếp. Điều này tương tự như việc thực hiện hàng đợi ưu tiên.

import bisect

def kLargest(A, k):
    '''returns list of k largest integers in A'''
    ret = []
    for i, a in enumerate(A):
        # For first k elements, simply construct sorted temp list
        # It is treated similarly to a priority queue
        if i < k:
            bisect.insort(ret, a) # properly inserts a into sorted list ret
        # Iterate over rest of array
        # Replace and update return array when more optimal element is found
        else:
            if a > ret[0]:
                del ret[0] # pop min element off queue
                bisect.insort(ret, a) # properly inserts a into sorted list ret
    return ret

Sử dụng với 100.000.000 phần tử và trường hợp xấu nhất là danh sách được sắp xếp:

>>> from so import kLargest
>>> kLargest(range(100000000), 100)
[99999900, 99999901, 99999902, 99999903, 99999904, 99999905, 99999906, 99999907,
 99999908, 99999909, 99999910, 99999911, 99999912, 99999913, 99999914, 99999915,
 99999916, 99999917, 99999918, 99999919, 99999920, 99999921, 99999922, 99999923,
 99999924, 99999925, 99999926, 99999927, 99999928, 99999929, 99999930, 99999931,
 99999932, 99999933, 99999934, 99999935, 99999936, 99999937, 99999938, 99999939,
 99999940, 99999941, 99999942, 99999943, 99999944, 99999945, 99999946, 99999947,
 99999948, 99999949, 99999950, 99999951, 99999952, 99999953, 99999954, 99999955,
 99999956, 99999957, 99999958, 99999959, 99999960, 99999961, 99999962, 99999963,
 99999964, 99999965, 99999966, 99999967, 99999968, 99999969, 99999970, 99999971,
 99999972, 99999973, 99999974, 99999975, 99999976, 99999977, 99999978, 99999979,
 99999980, 99999981, 99999982, 99999983, 99999984, 99999985, 99999986, 99999987,
 99999988, 99999989, 99999990, 99999991, 99999992, 99999993, 99999994, 99999995,
 99999996, 99999997, 99999998, 99999999]

Mất khoảng 40 giây để tính toán cho 100.000.000 phần tử, vì vậy tôi sợ phải làm điều đó với giá 1 tỷ. Công bằng mà nói, tôi đã cho nó ăn đầu vào trong trường hợp xấu nhất (trớ trêu thay là một mảng đã được sắp xếp).


0

Tôi thấy rất nhiều cuộc thảo luận O (N), vì vậy tôi đề xuất một cái gì đó khác biệt chỉ dành cho bài tập suy nghĩ.

Có bất kỳ thông tin được biết về bản chất của những con số này? Nếu nó là ngẫu nhiên trong tự nhiên, thì không đi xa hơn và nhìn vào các câu trả lời khác. Bạn sẽ không nhận được kết quả tốt hơn họ.

Tuy nhiên! Xem nếu bất cứ cơ chế điền danh sách nào cư trú trong danh sách đó theo một thứ tự cụ thể. Có phải chúng trong một mô hình được xác định rõ ràng mà bạn có thể biết chắc chắn rằng cường độ lớn nhất của số sẽ được tìm thấy trong một khu vực nhất định của danh sách hoặc trên một khoảng nhất định? Có thể có một mô hình cho nó. Nếu đúng là như vậy, ví dụ nếu chúng được đảm bảo ở một dạng phân phối bình thường với bướu đặc trưng ở giữa, luôn có xu hướng tăng lặp lại giữa các tập con được xác định, có một sự tăng vọt kéo dài tại một thời điểm T ở giữa dữ liệu giống như có thể là một tỷ lệ giao dịch nội gián hoặc thiết bị thất bại, hoặc có thể chỉ có một "đột biến" mỗi số thứ N như trong phân tích lực lượng sau thảm họa, bạn có thể giảm số lượng hồ sơ bạn phải kiểm tra đáng kể.

Có một số thực phẩm cho suy nghĩ nào. Có lẽ điều này sẽ giúp bạn cung cấp cho người phỏng vấn trong tương lai một câu trả lời chu đáo. Tôi biết tôi sẽ rất ấn tượng nếu ai đó hỏi tôi một câu hỏi như vậy để trả lời cho một vấn đề như thế này - nó sẽ cho tôi biết rằng họ đang nghĩ đến việc tối ưu hóa. Chỉ cần nhận ra rằng có thể không phải lúc nào cũng có khả năng tối ưu hóa.


0
Time ~ O(100 * N)
Space ~ O(100 + N)
  1. Tạo một danh sách trống gồm 100 vị trí trống

  2. Đối với mỗi số trong danh sách đầu vào:

    • Nếu số nhỏ hơn số đầu tiên, bỏ qua

    • Nếu không thì thay thế nó bằng số này

    • Sau đó, đẩy số qua trao đổi liền kề; cho đến khi nó nhỏ hơn cái tiếp theo

  3. Trả lại danh sách


Lưu ý: nếu log(input-list.size) + c < 100, thì cách tối ưu là sắp xếp danh sách đầu vào, sau đó chia 100 mục đầu tiên.


0

Độ phức tạp của chúng là O (N)

Đầu tiên tạo một mảng 100 ints khởi tạo phần tử đầu tiên của mảng này làm phần tử đầu tiên của các giá trị N, theo dõi chỉ số của phần tử hiện tại với một biến khác, gọi nó là CurrentBig

Lặp lại mặc dù các giá trị N

if N[i] > M[CurrentBig] {

M[CurrentBig]=N[i]; ( overwrite the current value with the newly found larger number)

CurrentBig++;      ( go to the next position in the M array)

CurrentBig %= 100; ( modulo arithmetic saves you from using lists/hashes etc.)

M[CurrentBig]=N[i];    ( pick up the current value again to use it for the next Iteration of the N array)

} 

khi hoàn tất, hãy in mảng M từ CurrentBig 100 lần modulo 100 :-) Đối với học sinh: đảm bảo rằng dòng cuối cùng của mã không xử lý dữ liệu hợp lệ ngay trước khi mã thoát


0

Một thuật toán O (n) khác -

Thuật toán tìm ra 100 lớn nhất bằng cách loại bỏ

xem xét tất cả các triệu số trong biểu diễn nhị phân của chúng. Bắt đầu từ bit quan trọng nhất. Việc tìm kiếm nếu MSB là 1 có thể được thực hiện bằng phép nhân boolean với một số thích hợp. Nếu có hơn 100 1 trong số một triệu này, hãy loại bỏ các số khác bằng số không. Bây giờ các số còn lại tiến hành với bit quan trọng nhất tiếp theo. giữ số lượng các số còn lại sau khi loại bỏ và tiến hành miễn là số này lớn hơn 100.

Hoạt động boolean chính có thể được thực hiện song song trên GPU


0

Tôi sẽ tìm ra ai có thời gian để đặt một tỷ số vào một mảng và bắn anh ta. Phải làm việc cho chính phủ. Ít nhất nếu bạn có một danh sách được liên kết, bạn có thể chèn một số vào giữa mà không cần di chuyển nửa tỷ để nhường chỗ. Thậm chí tốt hơn một Btree cho phép tìm kiếm nhị phân. Mỗi so sánh loại bỏ một nửa tổng số của bạn. Một thuật toán băm sẽ cho phép bạn đưa vào cấu trúc dữ liệu như bảng kiểm tra nhưng không tốt cho dữ liệu thưa thớt. Vì mục đích tốt nhất của bạn là có một mảng giải pháp gồm 100 số nguyên và theo dõi số thấp nhất trong mảng giải pháp của bạn để bạn có thể thay thế nó khi bạn gặp một số cao hơn trong mảng ban đầu. Bạn sẽ phải xem xét mọi phần tử trong mảng ban đầu giả sử nó không được sắp xếp để bắt đầu.


0

Bạn có thể làm điều đó trong O(n)thời gian. Chỉ cần lặp qua danh sách và theo dõi 100 số lớn nhất bạn đã thấy tại bất kỳ điểm nào và giá trị tối thiểu trong nhóm đó. Khi bạn tìm thấy một số mới lớn hơn số nhỏ nhất trong số mười của bạn, sau đó thay thế nó và cập nhật giá trị tối thiểu mới của bạn là 100 (có thể mất 100 thời gian liên tục để xác định số này mỗi khi bạn thực hiện, nhưng điều này không ảnh hưởng đến phân tích tổng thể ).


1
Cách tiếp cận này gần như giống hệt với cả câu trả lời nhiều nhất và thứ hai được đánh giá cao nhất cho câu hỏi này.
Bernhard Barker

0

Quản lý một danh sách riêng là công việc làm thêm và bạn phải di chuyển mọi thứ xung quanh toàn bộ danh sách mỗi khi bạn tìm thấy một sự thay thế khác. Chỉ cần qsort nó và lấy top 100.


-1 quicksort là O (n log n), đó chính xác là những gì OP đã làm và đang yêu cầu cải thiện. Bạn không cần phải quản lý một danh sách riêng biệt, chỉ một danh sách gồm 100 số. Đề xuất của bạn cũng có tác dụng phụ không mong muốn là thay đổi danh sách ban đầu hoặc sao chép nó. Đó là 4GiB hoặc hơn bộ nhớ, đã biến mất.

0
  1. Sử dụng phần tử thứ n để lấy phần tử thứ 100 O (n)
  2. Lặp lại lần thứ hai nhưng chỉ một lần và xuất ra mọi phần tử lớn hơn phần tử cụ thể này.

Xin lưu ý đặc biệt. bước thứ hai có thể dễ dàng tính toán song song! Và nó cũng sẽ hiệu quả khi bạn cần một triệu yếu tố lớn nhất.


0

Đó là một câu hỏi từ Google hoặc một số đại gia trong ngành khác. Có thể đoạn mã sau đây là câu trả lời đúng mà người phỏng vấn của bạn mong đợi. Chi phí thời gian và chi phí không gian phụ thuộc vào số lượng tối đa trong mảng đầu vào. Đối với đầu vào mảng int 32 bit, Chi phí không gian tối đa là 4 * 125M Byte, Chi phí thời gian là 5 * Tỷ.

public class TopNumber {
    public static void main(String[] args) {
        final int input[] = {2389,8922,3382,6982,5231,8934
                            ,4322,7922,6892,5224,4829,3829
                            ,6892,6872,4682,6723,8923,3492};
        //One int(4 bytes) hold 32 = 2^5 value,
        //About 4 * 125M Bytes
        //int sort[] = new int[1 << (32 - 5)];
        //Allocate small array for local test
        int sort[] = new int[1000];
        //Set all bit to 0
        for(int index = 0; index < sort.length; index++){
            sort[index] = 0;
        }
        for(int number : input){
            sort[number >>> 5] |= (1 << (number % 32));
        }
        int topNum = 0;
        outer:
        for(int index = sort.length - 1; index >= 0; index--){
            if(0 != sort[index]){
                for(int bit = 31; bit >= 0; bit--){
                    if(0 != (sort[index] & (1 << bit))){
                        System.out.println((index << 5) + bit);
                        topNum++;
                        if(topNum >= 3){
                            break outer;
                        }
                    }
                }
            }
        }
    }
}

0

tôi đã làm mã của riêng mình, không chắc nó là "người phỏng vấn" nó đang tìm kiếm

private static final int MAX=100;
 PriorityQueue<Integer> queue = new PriorityQueue<>(MAX);
        queue.add(array[0]);
        for (int i=1;i<array.length;i++)
        {

            if(queue.peek()<array[i])
            {
                if(queue.size() >=MAX)
                {
                    queue.poll();
                }
                queue.add(array[i]);

            }

        }

0

Cải tiến có thể.

Nếu tệp chứa 1 tỷ số, đọc nó có thể thực sự dài ...

Để cải thiện công việc này, bạn có thể:

  • Chia tệp thành n phần, Tạo n chủ đề, tạo n chủ đề tìm từng phần cho 100 số lớn nhất trong phần của tệp (sử dụng hàng đợi ưu tiên) và cuối cùng nhận được 100 số lớn nhất của tất cả các chủ đề đầu ra.
  • Sử dụng một cụm để thực hiện một nhiệm vụ như vậy, với một giải pháp như hadoop. Tại đây, bạn có thể chia tệp nhiều hơn và có đầu ra nhanh hơn cho tệp 1 tỷ (hoặc 10 ^ 12).

0

Đầu tiên lấy 1000 phần tử và thêm chúng trong một đống tối đa. Bây giờ lấy ra tối đa 100 yếu tố đầu tiên và lưu trữ nó ở đâu đó. Bây giờ, chọn 900 phần tử tiếp theo từ tệp và thêm chúng vào heap cùng với 100 phần tử cao nhất cuối cùng.

Tiếp tục lặp lại quá trình chọn 100 phần tử từ heap và thêm 900 phần tử từ tệp.

Lựa chọn cuối cùng của 100 yếu tố sẽ cung cấp cho chúng tôi tối đa 100 yếu tố từ một tỷ số.


-1

Bài toán: Tìm m phần tử lớn nhất của n mục trong đó n >>> m

Giải pháp đơn giản nhất, điều hiển nhiên đối với mọi người là chỉ cần thực hiện m vượt qua thuật toán sắp xếp bong bóng.

sau đó in ra n phần tử cuối cùng của mảng.

Điều này không đòi hỏi cấu trúc dữ liệu ngoài và sử dụng thuật toán mà mọi người đều biết.

Ước tính thời gian chạy là O (m * n). Câu trả lời tốt nhất cho đến nay là O (n log (m)), vì vậy giải pháp này không đắt hơn đáng kể cho m nhỏ.

Tôi không nói rằng điều này không thể được cải thiện, nhưng đây là giải pháp đơn giản nhất.


1
Không có cấu trúc dữ liệu bên ngoài? Còn mảng số tỷ để sắp xếp thì sao? Một mảng có kích thước này là một chi phí rất lớn trong cả thời gian để lấp đầy và không gian để lưu trữ. Điều gì sẽ xảy ra nếu tất cả các số "lớn" nằm ở cuối mảng sai? Bạn sẽ cần theo thứ tự 100 tỷ giao dịch hoán đổi để "bong bóng" chúng vào vị trí - một chi phí lớn khác ... Cuối cùng, M N = 100 tỷ so với M Log2 (N) = 6,64 tỷ, chênh lệch gần hai bậc. Có lẽ nên nghĩ lại cái này. Quét một lần trong khi duy trì cấu trúc dữ liệu của các số lớn nhất sẽ thực hiện đáng kể phương pháp này.
NealB
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.