Tạo một số nguyên không nằm trong số bốn tỷ số đã cho


691

Tôi đã được đưa ra câu hỏi phỏng vấn này:

Đưa ra một tệp đầu vào có bốn tỷ số nguyên, cung cấp một thuật toán để tạo một số nguyên không có trong tệp. Giả sử bạn có bộ nhớ 1 GB. Theo dõi những gì bạn sẽ làm nếu bạn chỉ có 10 MB bộ nhớ.

Phân tích của tôi:

Kích thước của tệp là 4 × 10 9 × 4 byte = 16 GB.

Chúng tôi có thể thực hiện sắp xếp bên ngoài, do đó cho chúng tôi biết phạm vi của các số nguyên.

Câu hỏi của tôi là cách tốt nhất để phát hiện số nguyên bị thiếu trong các bộ số nguyên lớn được sắp xếp là gì?

Sự hiểu biết của tôi (sau khi đọc tất cả các câu trả lời):

Giả sử chúng ta đang nói về số nguyên 32 bit, có 2 số nguyên 32 = 4 * 10 9 .

Trường hợp 1: chúng ta có 1 GB = 1 * 10 9 * 8 bit = bộ nhớ 8 tỷ bit.

Giải pháp:

Nếu chúng ta sử dụng một bit đại diện cho một số nguyên riêng biệt, nó là đủ. chúng ta không cần sắp xếp.

Thực hiện:

int radix = 8;
byte[] bitfield = new byte[0xffffffff/radix];
void F() throws FileNotFoundException{
    Scanner in = new Scanner(new FileReader("a.txt"));
    while(in.hasNextInt()){
        int n = in.nextInt();
        bitfield[n/radix] |= (1 << (n%radix));
    }

    for(int i = 0; i< bitfield.lenght; i++){
        for(int j =0; j<radix; j++){
            if( (bitfield[i] & (1<<j)) == 0) System.out.print(i*radix+j);
        }
    }
}

Trường hợp 2: Bộ nhớ 10 MB = 10 * 10 6 * 8 bit = 80 triệu bit

Giải pháp:

Đối với tất cả các tiền tố 16 bit có thể, có 2 số nguyên 16 = 65536, chúng ta cần 2 16 * 4 * 8 = 2 triệu bit. Chúng tôi cần xây dựng 65536 xô. Đối với mỗi nhóm, chúng ta cần 4 byte chứa tất cả các khả năng vì trường hợp xấu nhất là tất cả 4 tỷ số nguyên thuộc về cùng một nhóm.

  1. Xây dựng bộ đếm của mỗi nhóm thông qua lần đầu tiên đi qua tệp.
  2. Quét các thùng, tìm người đầu tiên có ít hơn 65536 lượt truy cập.
  3. Xây dựng các nhóm mới có tiền tố 16 bit cao được tìm thấy trong bước 2 đến lần chuyển thứ hai của tệp
  4. Quét các thùng được xây dựng trong bước 3, tìm thùng đầu tiên không có hit.

Mã này rất giống với ở trên.

Kết luận: Chúng tôi giảm bộ nhớ thông qua việc tăng vượt qua tập tin.


Một sự làm rõ cho những người đến muộn: Câu hỏi, như đã hỏi, không nói rằng có chính xác một số nguyên không có trong tập tin Ít nhất đó không phải là cách mà hầu hết mọi người giải thích nó. Mặc dù vậy, nhiều bình luận trong luồng nhận xét về sự biến đổi của nhiệm vụ. Thật không may, bình luận giới thiệu nó với chủ đề bình luận sau đó đã bị xóa bởi tác giả của nó, vì vậy bây giờ có vẻ như các câu trả lời mồ côi cho nó chỉ hiểu sai mọi thứ. Rất khó hiểu, xin lỗi.


32
@trashgod, sai rồi. Đối với 4294967295 số nguyên duy nhất, bạn sẽ còn 1 số nguyên. Để tìm thấy nó, bạn nên tính tổng tất cả các số nguyên và trừ nó từ tổng cộng được tính toán trước của tất cả các số nguyên có thể.
Nakilon

58
Đây là "viên ngọc" thứ hai trong "Ngọc trai lập trình" và tôi khuyên bạn nên đọc toàn bộ cuộc thảo luận trong cuốn sách. Xem sách.google.com.vn
Alok Singhal

8
@Richard một int 64 bit sẽ đủ lớn hơn.
cftarnas

79
int getMissingNumber(File inputFile) { return 4; }( tham khảo )
johnny

14
Không thành vấn đề khi bạn không thể lưu trữ tổng của tất cả các số nguyên từ 1 đến 2 ^ 32 vì loại số nguyên trong các ngôn ngữ như C / C ++ LUÔN LUÔN duy trì các thuộc tính như tính kết hợp và khả năng giao tiếp. Điều này có nghĩa là mặc dù tổng không phải là câu trả lời đúng, nhưng nếu bạn tính toán dự kiến ​​với mức tràn, tổng thực tế với mức tràn, và sau đó trừ đi, kết quả vẫn sẽ đúng (miễn là bản thân nó không tràn).
thedayturns

Câu trả lời:


529

Giả sử rằng "số nguyên" có nghĩa là 32 bit : 10 MB dung lượng là quá đủ để bạn đếm được có bao nhiêu số trong tệp đầu vào với bất kỳ tiền tố 16 bit đã cho nào, cho tất cả các tiền tố 16 bit có thể trong một lần đi qua tập tin đầu vào. Ít nhất một trong các thùng sẽ bị đánh ít hơn 2 16 lần. Thực hiện một lượt thứ hai để tìm ra số nào có thể có trong thùng đó đã được sử dụng.

Nếu nó có nghĩa là nhiều hơn 32 bit, nhưng vẫn có kích thước giới hạn : Thực hiện như trên, bỏ qua tất cả các số đầu vào xảy ra nằm ngoài phạm vi 32-bit (đã ký hoặc không dấu; lựa chọn của bạn).

Nếu "số nguyên" có nghĩa là số nguyên toán học : Đọc qua đầu vào một lần và theo dõi độ dài số lớn nhất của số dài nhất bạn từng thấy. Khi bạn hoàn thành, xuất tối đa cộng một số ngẫu nhiên có thêm một chữ số. (Một trong những số trong tệp có thể là một ký hiệu mất hơn 10 MB để thể hiện chính xác, nhưng nếu đầu vào là một tệp, thì ít nhất bạn có thể biểu thị độ dài của bất kỳ thứ gì phù hợp với nó).


24
Hoàn hảo. Câu trả lời đầu tiên của bạn chỉ cần 2 lần chuyển qua tập tin!
corsiKa

47
Một bignum 10 MB? Điều đó khá cực.
Đánh dấu tiền chuộc

12
@Legate, chỉ cần bỏ qua số lượng chồng chéo và không làm gì về chúng. Vì dù sao bạn cũng sẽ không xuất ra số lượng lớn, nên không cần theo dõi số nào bạn đã thấy.
hmakholm còn lại của Monica

12
Điểm hay của Giải pháp 1 là bạn có thể giảm bộ nhớ bằng cách tăng lượt đi.
Yousf

11
@Barry: Câu hỏi trên không chỉ ra rằng có chính xác một số bị thiếu. Nó cũng không nói các số trong tệp không lặp lại. (Theo câu hỏi thực sự được hỏi có lẽ là một ý tưởng tốt trong một cuộc phỏng vấn, phải không? ;-))
Christopher Creutzig

197

Các thuật toán được thông báo thống kê giải quyết vấn đề này bằng cách sử dụng ít lượt hơn so với các phương pháp xác định.

Nếu số nguyên rất lớn được cho phép thì người ta có thể tạo ra một số có khả năng là duy nhất trong thời gian O (1). Một số nguyên 128 bit giả ngẫu nhiên như GUID sẽ chỉ va chạm với một trong bốn tỷ số nguyên hiện có trong tập hợp ít hơn một trong số 64 tỷ tỷ trường hợp.

Nếu số nguyên bị giới hạn ở 32 bit thì người ta có thể tạo một số có khả năng là duy nhất trong một lần sử dụng ít hơn 10 MB. Tỷ lệ cược rằng một số nguyên 32 bit giả ngẫu nhiên sẽ va chạm với một trong 4 tỷ số nguyên hiện có là khoảng 93% (4e9 / 2 ^ 32). Tỷ lệ cược mà 1000 số nguyên giả ngẫu nhiên sẽ va chạm là ít hơn một trong 12.000 tỷ tỷ đồng (tỷ lệ cược một lần va chạm ^ 1000). Vì vậy, nếu một chương trình duy trì cấu trúc dữ liệu chứa 1000 ứng viên giả ngẫu nhiên và lặp qua các số nguyên đã biết, loại bỏ các kết quả khớp với các ứng cử viên, thì chắc chắn sẽ tìm thấy ít nhất một số nguyên không có trong tệp.


32
Tôi khá chắc chắn rằng các số nguyên bị chặn. Nếu họ không, thì ngay cả một lập trình viên mới bắt đầu cũng sẽ nghĩ đến thuật toán "lấy một dữ liệu để tìm số lượng tối đa và thêm 1 vào đó"
Adrian Petrescu

12
Theo nghĩa đen, việc đoán một kết quả ngẫu nhiên có thể sẽ không giúp bạn có nhiều điểm trong một cuộc phỏng vấn
Brian Gordon

6
@Adrian, giải pháp của bạn có vẻ hiển nhiên (và đối với tôi, tôi đã sử dụng nó trong câu trả lời của riêng mình) nhưng nó không rõ ràng với mọi người. Đây là một thử nghiệm tốt để xem liệu bạn có thể phát hiện ra các giải pháp rõ ràng hoặc nếu bạn sẽ làm phức tạp quá mức mọi thứ bạn chạm vào.
Đánh dấu tiền chuộc

19
@Brian: Tôi nghĩ giải pháp này là cả trí tưởng tượng và thực tế. Tôi cho một người sẽ đưa ra nhiều danh tiếng cho câu trả lời này.
Richard H

6
ah đây là ranh giới giữa các kỹ sư và nhà khoa học. Ben trả lời tuyệt vời!
TrojanName

142

Một cuộc thảo luận chi tiết về vấn đề này đã được thảo luận trong Jon Bentley "Cột 1. Bẻ khóa Oyster" Lập trình viên ngọc trai Addison-Wesley Trang.3-10

Bentley thảo luận về một số cách tiếp cận, bao gồm sắp xếp bên ngoài, Sắp xếp hợp nhất bằng cách sử dụng một số tệp bên ngoài, v.v., nhưng phương pháp tốt nhất mà Bentley gợi ý là một thuật toán vượt qua duy nhất sử dụng các trường bit , mà anh ta gọi một cách hài hước là "Wonder Sort" :) Đến với vấn đề, 4 tỷ số có thể được biểu diễn trong:

4 billion bits = (4000000000 / 8) bytes = about 0.466 GB

Mã để thực hiện bitet rất đơn giản: (lấy từ trang giải pháp )

#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 10000000
int a[1 + N/BITSPERWORD];

void set(int i) {        a[i>>SHIFT] |=  (1<<(i & MASK)); }
void clr(int i) {        a[i>>SHIFT] &= ~(1<<(i & MASK)); }
int  test(int i){ return a[i>>SHIFT] &   (1<<(i & MASK)); }

Thuật toán của Bentley thực hiện một lần chuyển qua tệp, setnhét bit thích hợp vào mảng và sau đó kiểm tra mảng này bằng cách sử dụng testmacro ở trên để tìm số bị thiếu.

Nếu bộ nhớ khả dụng dưới 0,466 GB, Bentley đề xuất thuật toán k-pass, phân chia đầu vào thành các phạm vi tùy thuộc vào bộ nhớ khả dụng. Lấy một ví dụ rất đơn giản, nếu chỉ có 1 byte (tức là bộ nhớ để xử lý 8 số) và phạm vi là từ 0 đến 31, chúng tôi chia số này thành các phạm vi từ 0 đến 7, 8-15, 16-22, v.v. và xử lý phạm vi này trong mỗi 32/8 = 4lần vượt qua.

HTH.


12
Tôi không biết cuốn sách này, nhưng không có lý do gì để gọi nó là "Wonder Sort", vì nó chỉ là một cái xô, với bộ đếm 1 bit.
flolo

3
Mặc dù dễ di chuyển hơn, mã này sẽ bị hủy bởi mã được viết để sử dụng các hướng dẫn vectơ hỗ trợ phần cứng . Tôi nghĩ rằng gcc có thể tự động chuyển đổi mã sang sử dụng các hoạt động vector trong một số trường hợp.
Brian Gordon

3
@brian Tôi không nghĩ Jon Bentley đã cho phép những thứ như vậy vào cuốn sách về thuật toán của mình.
David Heffernan

8
@BrianGordon, thời gian dành cho ram sẽ không đáng kể so với thời gian đọc tệp. Hãy quên việc tối ưu hóa nó.
Ian

1
@BrianGordon: Hay bạn đã nói về vòng lặp ở cuối để tìm bit unset đầu tiên? Đúng, vectơ sẽ tăng tốc độ lên, nhưng lặp lại trên bitfield với số nguyên 64 bit, tìm kiếm một != -1băng thông vẫn sẽ bão hòa bộ nhớ chạy trên một lõi đơn (đây là SIMD-trong-một-thanh ghi, SWAR, với các bit là các phần tử). (Đối với các thiết kế Intel / AMD gần đây). Bạn chỉ phải tìm ra bit nào không được đặt sau khi bạn tìm thấy vị trí 64 bit chứa nó. (Và vì điều đó bạn có thể not / lzcnt.) Điểm công bằng rằng việc lặp qua kiểm tra một bit có thể không được tối ưu hóa tốt.
Peter Cordes

120

Vì vấn đề không chỉ định rằng chúng tôi phải tìm số nhỏ nhất có thể không có trong tệp, chúng tôi chỉ có thể tạo một số dài hơn chính tệp đầu vào. :)


6
Trừ khi số lớn nhất trong tệp là max int thì bạn sẽ chỉ tràn
KBusc

Kích thước của tệp đó trong chương trình Thế giới thực có thể cần tạo một số nguyên mới và nối nó vào tệp "số nguyên đã sử dụng" 100 lần?
Michael

2
Tôi đã suy nghĩ điều này. Giả sử int32bit, chỉ là đầu ra 2^64-1. Làm xong.
imallett

1
Nếu đó là một int trên mỗi dòng tr -d '\n' < nums.txt > new_num.txt:: D
Shon

56

Đối với biến thể RAM 1 GB, bạn có thể sử dụng một vectơ bit. Bạn cần phân bổ 4 tỷ bit == 500 byte byte. Đối với mỗi số bạn đọc từ đầu vào, đặt bit tương ứng thành '1'. Khi bạn đã hoàn tất, lặp lại các bit, tìm cái đầu tiên vẫn là '0'. Chỉ số của nó là câu trả lời.


4
Phạm vi số trong đầu vào không được chỉ định. Thuật toán này hoạt động như thế nào nếu đầu vào bao gồm tất cả các số chẵn từ 8 tỷ đến 16 tỷ?
Đánh dấu tiền chuộc

27
@Mark, chỉ cần bỏ qua các đầu vào nằm ngoài phạm vi 0..2 ^ 32. Dù sao thì bạn cũng sẽ không xuất ra bất kỳ cái nào trong số chúng, vì vậy không cần phải nhớ chúng nên tránh cái nào.
hmakholm còn lại của Monica

@Mark bất kỳ thuật toán nào bạn sử dụng để xác định cách ánh xạ chuỗi 32 bit đến một số thực tùy thuộc vào bạn. Quá trình vẫn như vậy. Sự khác biệt duy nhất là cách bạn in nó như một số thực lên màn hình.
corsiKa

4
Thay vì tự lặp lại, bạn có thể sử dụng bitSet.nextClearBit(0): download.oracle.com/javase/6/docs/api/java/util/
mẹo

3
Sẽ rất hữu ích khi đề cập rằng, bất kể phạm vi của các số nguyên, ít nhất một bit được đảm bảo là 0 ở cuối đường chuyền. Điều này là do nguyên tắc pigeonhole.
Rafał Dowgird

46

Nếu chúng là số nguyên 32 bit (có thể từ sự lựa chọn ~ 4 tỷ số gần với 2 32 ), danh sách 4 tỷ số của bạn sẽ chiếm tối đa 93% số nguyên có thể (4 * 10 9 / (2 32 ) ). Vì vậy, nếu bạn tạo một mảng bit gồm 2 32 bit với mỗi bit được khởi tạo thành 0 (sẽ chiếm 2 29 byte ~ 500 MB RAM; hãy nhớ một byte = 2 3 bit = 8 bit), hãy đọc qua danh sách số nguyên của bạn và với mỗi int đặt phần tử mảng bit tương ứng từ 0 đến 1; và sau đó đọc qua mảng bit của bạn và trả về bit đầu tiên vẫn là 0.

Trong trường hợp bạn có ít RAM hơn (~ 10 MB), giải pháp này cần được sửa đổi một chút. 10 MB ~ 83886080 bit vẫn đủ để thực hiện một mảng bit cho tất cả các số trong khoảng từ 0 đến 83886079. Vì vậy, bạn có thể đọc qua danh sách ints của mình; và chỉ ghi #s nằm trong khoảng từ 0 đến 83886079 trong mảng bit của bạn. Nếu các số được phân phối ngẫu nhiên; với xác suất áp đảo (nó khác 100% vào khoảng 10 -2592069 ), bạn sẽ tìm thấy một int bị thiếu). Trên thực tế, nếu bạn chỉ chọn các số từ 1 đến 2048 (chỉ với 256 byte RAM), bạn vẫn sẽ thấy một số bị thiếu một tỷ lệ áp đảo (99.999999999999999999999999999999999999999999999999999999999999999%).

Nhưng hãy nói thay vì có khoảng 4 tỷ số; bạn có thứ gì đó như 2 32 - 1 số và ít hơn 10 MB RAM; Vì vậy, bất kỳ phạm vi nhỏ nào của int chỉ có một khả năng nhỏ là không chứa số.

Nếu bạn được đảm bảo rằng mỗi int trong danh sách là duy nhất, bạn có thể tính tổng các số và trừ tổng với một # bị thiếu cho tổng số đầy đủ (½) (2 32 ) (2 32 - 1) = 9223372034707292160 để tìm số int bị thiếu . Tuy nhiên, nếu một int xảy ra hai lần thì phương thức này sẽ thất bại.

Tuy nhiên, bạn luôn có thể phân chia và chinh phục. Một phương pháp ngây thơ, sẽ là đọc qua mảng và đếm số lượng số trong nửa đầu (0 đến 2 31 -1) và nửa sau (2 31 , 2 32 ). Sau đó chọn phạm vi có số lượng ít hơn và lặp lại chia phạm vi đó thành một nửa. (Giả sử nếu có hai số nhỏ hơn trong (2 31 , 2 32 ) thì tìm kiếm tiếp theo của bạn sẽ đếm các số trong phạm vi (2 31 , 3 * 2 30 -1), (3 * 2 30 , 2 32 ). lặp lại cho đến khi bạn tìm thấy một phạm vi có số không và bạn có câu trả lời của mình. Nên đọc O (lg N) ~ 32 lần đọc qua mảng.

Phương pháp đó không hiệu quả. Chúng tôi chỉ sử dụng hai số nguyên trong mỗi bước (hoặc khoảng 8 byte RAM với số nguyên 4 byte (32 bit)). Một phương pháp tốt hơn sẽ là chia thành sqrt (2 32 ) = 2 16 = 65536 thùng, mỗi thùng có 65536 số trong một thùng. Mỗi thùng yêu cầu 4 byte để lưu trữ số lượng của nó, vì vậy bạn cần 2 18 byte = 256 kB. Vậy bin 0 là (0 đến 65535 = 2 16 -1), bin 1 là (2 16 = 65536 đến 2 * 2 16 -1 = 131071), bin 2 là (2 * 2 16 = 131072 đến 3 * 2 16 - 1 = 196607). Trong python bạn có một cái gì đó như:

import numpy as np
nums_in_bin = np.zeros(65536, dtype=np.uint32)
for N in four_billion_int_array:
    nums_in_bin[N // 65536] += 1
for bin_num, bin_count in enumerate(nums_in_bin):
    if bin_count < 65536:
        break # we have found an incomplete bin with missing ints (bin_num)

Đọc qua danh sách số nguyên ~ 4 tỷ; và đếm xem có bao nhiêu số int rơi vào mỗi 2 16 thùng và tìm một số chưa hoàn thành mà không có tất cả 65536 số. Sau đó, bạn đọc lại danh sách 4 tỷ số nguyên một lần nữa; nhưng lần này chỉ thông báo khi số nguyên nằm trong phạm vi đó; lật một chút khi bạn tìm thấy chúng.

del nums_in_bin # allow gc to free old 256kB array
from bitarray import bitarray
my_bit_array = bitarray(65536) # 32 kB
my_bit_array.setall(0)
for N in four_billion_int_array:
    if N // 65536 == bin_num:
        my_bit_array[N % 65536] = 1
for i, bit in enumerate(my_bit_array):
    if not bit:
        print bin_num*65536 + i
        break

3
Thật là một câu trả lời tuyệt vời. Điều này thực sự sẽ làm việc; và có kết quả đảm bảo.
Jonathan Dickinson

@dr jimbob, nếu chỉ có một số trong một thùng và số đó có 65535 bản sao thì sao? Nếu vậy, thùng vẫn sẽ tính 65536, nhưng tất cả các số 65536 đều giống nhau.
Alcott

@ Alcott - Tôi giả sử bạn có 2 ^ 32-1 (hoặc ít hơn), vì vậy theo nguyên tắc pigeonhole, bạn được đảm bảo có một thùng có số lượng ít hơn 65536 để kiểm tra chi tiết hơn. Chúng tôi đang cố gắng tìm chỉ một số nguyên còn thiếu, không phải tất cả chúng. Nếu bạn có 2 ^ 32 số trở lên, bạn không thể đảm bảo số nguyên bị thiếu và sẽ không thể sử dụng phương thức này (hoặc có một bảo đảm ngay từ đầu có một số nguyên bị thiếu). Đặt cược tốt nhất của bạn sau đó sẽ là sức mạnh vũ phu (ví dụ: đọc qua mảng 32 lần; kiểm tra 65536 #s lần đầu tiên và dừng lại khi tìm thấy câu trả lời).
dr jimbob

Phương pháp thông minh trên 16 / dưới-16 đã được Henning đăng tải trước đó: stackoverflow.com/a/7153822/224132 . Mặc dù vậy, tôi thích ý tưởng bổ trợ cho một bộ số nguyên duy nhất thiếu chính xác một thành viên.
Peter Cordes

3
@PeterCordes - Vâng, giải pháp của Henning có trước tôi, nhưng tôi nghĩ câu trả lời của tôi vẫn hữu ích (làm việc thông qua một số điều chi tiết hơn). Điều đó nói rằng, Jon Bentley trong cuốn sách Lập trình viên ngọc trai đã đề xuất một tùy chọn nhiều đường cho vấn đề này (xem câu trả lời của cây nho) trước khi stackoverflow tồn tại (không phải tôi cho rằng một trong hai chúng tôi đã đánh cắp một cách có ý thức từ đó hoặc rằng Bentley là người đầu tiên phân tích vấn đề này - đó là một giải pháp khá tự nhiên để phát triển). Hai đường chuyền có vẻ tự nhiên nhất khi giới hạn là bạn không còn đủ bộ nhớ cho giải pháp 1 đường chuyền với một mảng bit khổng lồ.
dr jimbob

37

Tại sao làm cho nó phức tạp như vậy? Bạn yêu cầu một số nguyên không có trong tập tin?

Theo các quy tắc được chỉ định, điều duy nhất bạn cần lưu trữ là số nguyên lớn nhất mà bạn gặp phải cho đến nay trong tệp. Khi toàn bộ tệp đã được đọc, trả về số 1 lớn hơn số đó.

Không có rủi ro khi đạt maxint hoặc bất cứ điều gì, bởi vì theo quy tắc, không có giới hạn về kích thước của số nguyên hoặc số được thuật toán trả về.


4
Điều này sẽ hoạt động trừ khi max int có trong tệp, điều này hoàn toàn có thể ...
PearsonArtPhoto

13
Các quy tắc không chỉ định rằng nó là 32 bit hoặc 64 bit hoặc bất cứ điều gì, vì vậy theo các quy tắc được chỉ định, không có int int tối đa. Số nguyên không phải là một thuật ngữ máy tính, nó là một thuật ngữ toán học xác định các số nguyên dương hoặc âm.
Pete

Đúng như vậy, nhưng người ta không thể cho rằng đó là số 64 bit hoặc ai đó sẽ không lẻn vào số int tối đa chỉ để nhầm lẫn các thuật toán như vậy.
PearsonArtPhoto

24
Toàn bộ khái niệm "max int" không hợp lệ trong ngữ cảnh nếu không có ngôn ngữ lập trình nào được chỉ định. ví dụ: nhìn vào định nghĩa của Python về một số nguyên dài. Nó là vô hạn. Không có mái nhà. Bạn luôn có thể thêm một. Bạn đang giả sử nó đang được thực hiện bằng ngôn ngữ có giá trị tối đa được phép cho một số nguyên.
Pete

32

Điều này có thể được giải quyết trong rất ít không gian bằng cách sử dụng một biến thể của tìm kiếm nhị phân.

  1. Bắt đầu với phạm vi số được phép, 0đến 4294967295.

  2. Tính trung điểm.

  3. Lặp lại tệp, đếm xem có bao nhiêu số bằng nhau, nhỏ hơn hoặc cao hơn giá trị trung điểm.

  4. Nếu không có số nào bằng nhau, bạn đã hoàn thành. Số trung điểm là câu trả lời.

  5. Mặt khác, chọn phạm vi có số ít nhất và lặp lại từ bước 2 với phạm vi mới này.

Điều này sẽ yêu cầu tối đa 32 lần quét tuyến tính thông qua tệp, nhưng nó sẽ chỉ sử dụng một vài byte bộ nhớ để lưu trữ phạm vi và số lượng.

Điều này về cơ bản giống như giải pháp của Henning , ngoại trừ việc sử dụng hai thùng thay vì 16k.


2
Đó là những gì tôi đã bắt đầu, trước khi tôi bắt đầu tối ưu hóa cho các tham số đã cho.
hmakholm còn lại trên Monica

@Henning: Tuyệt. Đây là một ví dụ hay về thuật toán, nơi dễ dàng điều chỉnh sự đánh đổi trong không gian.
hammar

@hammar, nhưng nếu có những con số xuất hiện nhiều hơn một lần thì sao?
Alcott

@ Alcott: sau đó thuật toán sẽ chọn thùng dày hơn thay vì thùng sperer, nhưng theo nguyên tắc pigeonhole, nó không bao giờ có thể chọn một thùng hoàn toàn đầy đủ. (Số nhỏ hơn trong hai số sẽ luôn nhỏ hơn phạm vi bin.)
Peter Cordes

27

EDIT Ok, điều này không được suy nghĩ thấu đáo vì nó giả sử các số nguyên trong tệp tuân theo một số phân phối tĩnh. Rõ ràng họ không cần, nhưng ngay cả khi đó người ta cũng nên thử điều này:


Có ≈4,3 tỷ số nguyên 32 bit. Chúng tôi không biết làm thế nào chúng được phân phối trong tệp, nhưng trường hợp xấu nhất là trường hợp có entropy Shannon cao nhất: phân phối bằng nhau. Trong trường hợp này, xác suất cho bất kỳ một số nguyên nào không xảy ra trong tệp là

((2³²-1) / 2³²) ⁴ ⁰⁰⁰ ⁰⁰⁰ ⁰⁰⁰ ≈ .4

Entropy Shannon càng thấp, xác suất trung bình càng cao, nhưng ngay cả trong trường hợp xấu nhất này, chúng tôi có cơ hội 90% để tìm một số không xuất hiện sau 5 lần đoán với số nguyên ngẫu nhiên. Chỉ cần tạo số như vậy với trình tạo giả ngẫu nhiên, lưu trữ chúng trong danh sách. Sau đó đọc int sau int và so sánh nó với tất cả các dự đoán của bạn. Khi có một trận đấu, loại bỏ danh sách này. Sau khi đã xem qua tất cả các tập tin, rất có thể bạn sẽ có nhiều hơn một lần đoán. Sử dụng bất kỳ trong số họ. Trong trường hợp hiếm hoi (10% ngay cả trong trường hợp xấu nhất) không đoán được, hãy lấy một bộ số nguyên ngẫu nhiên mới, có lẽ nhiều hơn lần này (10-> 99%).

Tiêu thụ bộ nhớ: vài chục byte, độ phức tạp: O (n), tổng phí: không thể truy cập được vì hầu hết thời gian sẽ được sử dụng trong các truy cập đĩa cứng không thể tránh khỏi thay vì so sánh ints.


Trường hợp xấu nhất thực tế, khi chúng ta không giả sử phân phối tĩnh, là mọi số nguyên xảy ra tối đa. một lần, bởi vì sau đó chỉ có 1 - 4000000000 / 2³² 6% số nguyên không xảy ra trong tệp. Vì vậy, bạn sẽ cần thêm một số dự đoán, nhưng điều đó sẽ không tốn nhiều bộ nhớ.


5
Tôi rất vui khi thấy người khác nghĩ về điều này, nhưng tại sao nó lại ở dưới đây? Đây là 1-pass algo 10 MB là đủ cho các lần đoán 2,5 M và 93% ^ 2,5M ≈ 10 ^ -79000 thực sự là một cơ hội không đáng kể khi cần quét lần thứ hai. Do chi phí tìm kiếm nhị phân, nó sẽ nhanh hơn nếu bạn sử dụng ít dự đoán hơn! Điều này là tối ưu trong cả thời gian và không gian.
Potatoswatter

1
@Potatoswatter: tốt bạn đã đề cập đến tìm kiếm nhị phân. Điều đó có thể không đáng giá khi chỉ sử dụng 5 lần đoán, nhưng chắc chắn là ở mức 10 trở lên. Bạn thậm chí có thể đoán 2 M, nhưng sau đó bạn nên lưu trữ chúng trong bộ băm để lấy O (1) cho tìm kiếm.
leftaroundabout

1
@Potatoswatter Câu trả lời tương đương của Ben Haley nằm ở gần đầu
Brian Gordon

1
Tôi thích cách tiếp cận này, nhưng sẽ đề xuất một cải tiến tiết kiệm bộ nhớ: nếu ai đó có sẵn N bit lưu trữ được lập chỉ mục, cộng với một số lưu trữ không đổi, hãy xác định hàm xáo trộn 32 bit có thể đảo ngược cấu hình (hoán vị), chọn một hoán vị tùy ý và xóa tất cả bit được lập chỉ mục. Sau đó đọc từng số từ tệp, xáo trộn nó và nếu kết quả nhỏ hơn N, hãy đặt bit tương ứng. Nếu bất kỳ bit nào không được đặt ở cuối tệp, hãy đảo ngược chức năng xáo trộn trên chỉ mục của nó. Với bộ nhớ 64KB, người ta có thể kiểm tra hiệu quả hơn 512.000 số về tính khả dụng trong một lần chạy.
supercat

2
Tất nhiên, với thuật toán này, trường hợp xấu nhất là trường hợp các số được tạo bởi cùng một trình tạo số ngẫu nhiên mà bạn đang sử dụng. Giả sử bạn có thể đảm bảo không phải như vậy, chiến thuật tốt nhất của bạn là sử dụng trình tạo số ngẫu nhiên đồng quy tuyến tính để tạo danh sách của bạn, để bạn sẽ đi qua không gian số theo cách giả ngẫu nhiên. Điều đó có nghĩa là nếu bạn bằng cách nào đó thất bại, bạn có thể tiếp tục tạo số cho đến khi bạn bao quát toàn bộ phạm vi số nguyên (đã tìm thấy một khoảng trống), mà không bao giờ lặp lại nỗ lực của bạn.
Dewi Morgan

25

Nếu bạn thiếu một số nguyên trong phạm vi [0, 2 ^ x - 1] thì chỉ cần xor tất cả chúng lại với nhau. Ví dụ:

>>> 0 ^ 1 ^ 3
2
>>> 0 ^ 1 ^ 2 ^ 3 ^ 4 ^ 6 ^ 7
5

(Tôi biết điều này không trả lời chính xác câu hỏi , nhưng đó là một câu trả lời tốt cho một câu hỏi rất giống nhau.)


1
Có, thật dễ dàng để chứng minh [ ] hoạt động khi thiếu một số nguyên, nhưng nó thường bị lỗi nếu thiếu nhiều hơn một số nguyên. Ví dụ: 0 ^ 1 ^ 3 ^ 4 ^ 6 ^ 7là 0. [ Viết 2 x cho công suất 2 đến x'th và a ^ b cho a xor b, xor của tất cả k <2 x là 0 - k ^ ~ k = (2 ^ x) - 1 cho k <2 ^ (x-1) và k ^ ~ k ^ j ^ ~ j = 0 khi j = k + 2 ** (x-2) - vì vậy xor của tất cả nhưng một số là giá trị của người mất tích]
James Waldby - jwpat7

2
Như tôi đã đề cập trong một nhận xét về câu trả lời của ircmaxell: Vấn đề không nói là "thiếu một số", nó nói rằng tìm một số không có trong 4 tỷ số trong tệp. Nếu chúng tôi giả sử số nguyên 32 bit, thì có thể thiếu khoảng 300 triệu số trong tệp. Khả năng xor của các số hiện tại khớp với một số còn thiếu chỉ khoảng 7%.
James Waldby - jwpat7

Đây là câu trả lời tôi đã nghĩ đến khi tôi đọc câu hỏi ban đầu, nhưng khi kiểm tra kỹ hơn tôi nghĩ câu hỏi này còn mơ hồ hơn thế này. FYI, đây là câu hỏi tôi đã nghĩ đến: stackoverflow.com/questions353185/NH
Lee Netherton

18

Họ có thể đang tìm kiếm xem bạn đã nghe nói về Bộ lọc Bloom có thể xác định rất hiệu quả nếu một giá trị không phải là một phần của tập hợp lớn, (nhưng chỉ có thể xác định với xác suất cao đó là thành viên của tập hợp.)


4
Với hơn 90% giá trị có thể được đặt, Bộ lọc Bloom của bạn có thể cần phải suy biến thành bitfield để có nhiều câu trả lời được sử dụng. Nếu không, bạn sẽ kết thúc với một chuỗi bit hoàn toàn vô dụng.
Christopher Creutzig

@Christopher Sự hiểu biết của tôi về các bộ lọc Bloom là bạn không nhận được một bitarray đầy cho đến khi bạn đạt 100%
Paul

... Nếu không, bạn sẽ nhận được tiêu cực sai.
Paul

@Paul một mảng bit đầy cung cấp cho bạn dương tính giả, được phép. Trong trường hợp này, bộ lọc nở rất có thể bị suy biến đối với trường hợp dung dịch có giá trị âm sẽ trả về giá trị dương.
ataylor

1
@Paul: Bạn có thể nhận được một bitarray đầy ngay khi số lượng hàm băm nhân với số lượng mục nhập lớn bằng chiều dài của trường của bạn. Tất nhiên, đó sẽ là một trường hợp đặc biệt, nhưng xác suất sẽ tăng khá nhanh.
Christopher Creutzig

17

Dựa trên từ ngữ hiện tại trong câu hỏi ban đầu, giải pháp đơn giản nhất là:

Tìm giá trị tối đa trong tệp, sau đó thêm 1 vào nó.


5
Nếu MAXINT được bao gồm trong tệp thì sao?
Petr Peller

@Petr Peller: Một thư viện BIGINT về cơ bản sẽ loại bỏ các giới hạn về kích thước số nguyên.
oosterwal

2
@oosterwal, nếu câu trả lời này được cho phép, thậm chí bạn không cần phải đọc tệp - chỉ cần in số lượng lớn nhất có thể.
Nakilon

1
@oosterwal, nếu số lượng lớn ngẫu nhiên của bạn là số lượng lớn nhất bạn có thể in và nó nằm trong tệp, thì nhiệm vụ này không thể được giải quyết.
Nakilon

3
@Nakilon: +1 Điểm của bạn đã được thực hiện. Nó gần tương đương với việc tìm ra tổng số chữ số trong tệp và in một số có nhiều chữ số đó.
oosterwal

14

Sử dụng a BitSet. 4 tỷ số nguyên (giả sử có tới 2 ^ 32 số nguyên) được đóng gói vào Bit set ở mức 8 mỗi byte là 2 ^ 32/2 ^ 3 = 2 ^ 29 = khoảng 0,5 Gb.

Để thêm chi tiết hơn một chút - mỗi khi bạn đọc một số, hãy đặt bit tương ứng trong Bitset. Sau đó, vượt qua BitSet để tìm số đầu tiên không có. Trên thực tế, bạn có thể làm điều này hiệu quả bằng cách liên tục chọn một số ngẫu nhiên và kiểm tra nếu có.

Trên thực tế BitSet.nextClearBit (0) sẽ cho bạn biết bit không được đặt đầu tiên.

Nhìn vào API BitSet, nó dường như chỉ hỗ trợ 0..MAX_INT, do đó bạn có thể cần 2 BitSets - một cho số + và một số đã có - nhưng yêu cầu bộ nhớ không thay đổi.


1
Hoặc nếu bạn không muốn sử dụng BitSet... hãy thử một mảng bit. Có làm điều tương tự không?)
jcolebrand

12

Nếu không có giới hạn kích thước, cách nhanh nhất là lấy độ dài của tệp và tạo độ dài của tệp + 1 số chữ số ngẫu nhiên (hoặc chỉ "1111 ..." s). Ưu điểm: bạn thậm chí không cần đọc tệp và bạn có thể giảm thiểu sử dụng bộ nhớ gần như bằng không. Nhược điểm: Bạn sẽ in hàng tỷ chữ số.

Tuy nhiên, nếu yếu tố duy nhất là giảm thiểu việc sử dụng bộ nhớ và không có gì khác quan trọng, đây sẽ là giải pháp tối ưu. Nó thậm chí có thể giúp bạn nhận được giải thưởng "lạm dụng quy tắc tồi tệ nhất".


11

Nếu chúng tôi giả định rằng phạm vi số sẽ luôn là 2 ^ n (công suất chẵn là 2), thì độc quyền hoặc sẽ hoạt động (như được hiển thị bởi một người đăng khác). Theo như tại sao, hãy chứng minh điều đó:

Học thuyết

Với bất kỳ phạm vi số 0 dựa trên nào có 2^ncác phần tử bị thiếu một phần tử, bạn có thể tìm thấy phần tử bị thiếu đó bằng cách đơn giản xor-ing các giá trị đã biết với nhau để mang lại số bị thiếu.

Bằng chứng

Hãy xem n = 2. Với n = 2, chúng ta có thể biểu diễn 4 số nguyên duy nhất: 0, 1, 2, 3. Chúng có một mẫu bit là:

  • 0 - 00
  • 1 - 01
  • 2 - 10
  • 3 - 11

Bây giờ, nếu chúng ta nhìn, mỗi bit được đặt chính xác hai lần. Do đó, vì nó được đặt số lần chẵn và độc quyền hoặc số sẽ mang lại 0. Nếu một số bị thiếu, độc quyền hoặc sẽ mang lại một số mà khi độc quyền với số bị thiếu sẽ dẫn đến 0. Do đó, số bị thiếu và số độc quyền kết quả là hoàn toàn giống nhau. Nếu chúng ta xóa 2, xor kết quả sẽ là10 (hoặc 2).

Bây giờ, hãy nhìn vào n + 1. Hãy gọi số lần mỗi bit được thiết lập trong n, xvà số lần mỗi bit được thiết lập trong n+1 y. Giá trị của ysẽ bằng y = x * 2vì có xcác phần tử với n+1bit được đặt thành 0 và xcác phần tử có n+1bit được đặt thành 1. Và vì 2xsẽ luôn là số chẵn, n+1sẽ luôn có mỗi bit được đặt số lần chẵn.

Do đó, vì n=2hoạt động và n+1hoạt động, phương thức xor sẽ hoạt động cho tất cả các giá trị của n>=2.

Thuật toán cho các phạm vi dựa trên 0

Điều này khá đơn giản. Nó sử dụng 2 * n bit bộ nhớ, do đó, đối với mọi phạm vi <= 32, 2 số nguyên 32 bit sẽ hoạt động (bỏ qua mọi bộ nhớ được sử dụng bởi bộ mô tả tệp). Và nó thực hiện một lần duy nhất của tập tin.

long supplied = 0;
long result = 0;
while (supplied = read_int_from_file()) {
    result = result ^ supplied;
}
return result;

Thuật toán cho các phạm vi dựa trên tùy ý

Thuật toán này sẽ hoạt động cho các phạm vi của bất kỳ số bắt đầu nào cho bất kỳ số kết thúc nào, miễn là tổng phạm vi bằng 2 ^ n ... Điều này về cơ bản sẽ căn cứ lại phạm vi để có mức tối thiểu là 0. Nhưng nó yêu cầu 2 lần vượt qua thông qua tệp (cái đầu tiên để lấy mức tối thiểu, cái thứ hai để tính int còn thiếu).

long supplied = 0;
long result = 0;
long offset = INT_MAX;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    result = result ^ (supplied - offset);
}
return result + offset;

Phạm vi tùy ý

Chúng ta có thể áp dụng phương pháp sửa đổi này cho một tập hợp các phạm vi tùy ý, vì tất cả các phạm vi sẽ vượt qua sức mạnh 2 ^ n ít nhất một lần. Điều này chỉ hoạt động nếu có một bit bị thiếu duy nhất. Phải mất 2 lượt của một tệp chưa được sắp xếp, nhưng nó sẽ tìm thấy số bị thiếu duy nhất mỗi lần:

long supplied = 0;
long result = 0;
long offset = INT_MAX;
long n = 0;
double temp;
while (supplied = read_int_from_file()) {
    if (supplied < offset) {
        offset = supplied;
    }
}
reset_file_pointer();
while (supplied = read_int_from_file()) {
    n++;
    result = result ^ (supplied - offset);
}
// We need to increment n one value so that we take care of the missing 
// int value
n++
while (n == 1 || 0 != (n & (n - 1))) {
    result = result ^ (n++);
}
return result + offset;

Về cơ bản, căn cứ lại phạm vi khoảng 0. Sau đó, nó đếm số lượng giá trị chưa được sắp xếp để nối thêm khi nó tính toán độc quyền hoặc. Sau đó, nó thêm 1 vào số lượng các giá trị chưa được sắp xếp để chăm sóc giá trị còn thiếu (đếm giá trị còn thiếu). Sau đó, tiếp tục xending giá trị n, tăng thêm 1 lần cho đến khi n là lũy thừa của 2. Kết quả sau đó được dựa lại vào cơ sở ban đầu. Làm xong.

Đây là thuật toán tôi đã thử nghiệm trong PHP (sử dụng một mảng thay vì một tệp, nhưng cùng một khái niệm):

function find($array) {
    $offset = min($array);
    $n = 0;
    $result = 0;
    foreach ($array as $value) {
        $result = $result ^ ($value - $offset);
        $n++;
    }
    $n++; // This takes care of the missing value
    while ($n == 1 || 0 != ($n & ($n - 1))) {
        $result = $result ^ ($n++);
    }
    return $result + $offset;
}

Fed trong một mảng với bất kỳ phạm vi giá trị nào (tôi đã kiểm tra bao gồm cả phủ định) với một trong phạm vi đó bị thiếu, nó tìm thấy giá trị chính xác mỗi lần.

Cách tiếp cận khác

Vì chúng ta có thể sử dụng phân loại bên ngoài, tại sao không kiểm tra khoảng cách? Nếu chúng ta giả sử tệp được sắp xếp trước khi chạy thuật toán này:

long supplied = 0;
long last = read_int_from_file();
while (supplied = read_int_from_file()) {
    if (supplied != last + 1) {
        return last + 1;
    }
    last = supplied;
}
// The range is contiguous, so what do we do here?  Let's return last + 1:
return last + 1;

3
Vấn đề không nói "thiếu một số", nó nói rằng tìm một số không có trong 4 tỷ số trong tệp. Nếu chúng tôi giả sử số nguyên 32 bit, thì có thể thiếu khoảng 300 triệu số trong tệp. Khả năng xor của các số hiện tại khớp với một số còn thiếu chỉ khoảng 7%.
James Waldby - jwpat7

Nếu bạn có một phạm vi liền kề nhưng thiếu-một không dựa trên không, hãy thêm thay vì xor. sum(0..n) = n*(n+1)/2. Vì vậy missing = nmax*(nmax+1)/2 - nmin*(nmin+1)/2 - sum(input[]). (tổng hợp ý tưởng từ câu trả lời của @ hammar.)
Peter Cordes

9

Câu hỏi lừa, trừ khi nó được trích dẫn không đúng. Chỉ cần đọc qua tệp một lần để lấy số nguyên tối đa nvà trả về n+1.

Tất nhiên, bạn cần một kế hoạch dự phòng trong trường hợp n+1gây ra tràn số nguyên.


3
Đây là một giải pháp hoạt động ... ngoại trừ khi nó không. Hữu ích! :-)
dty

Trừ khi nó được trích dẫn không chính xác, câu hỏi không đặt ra ràng buộc về loại số nguyên, hoặc thậm chí trên ngôn ngữ được sử dụng. Nhiều ngôn ngữ hiện đại có số nguyên chỉ giới hạn bởi bộ nhớ khả dụng. Nếu số nguyên lớn nhất trong tệp là> 10MB, may mắn khó khăn, không thể thực hiện nhiệm vụ cho trường hợp thứ hai. Giải pháp yêu thích của tôi.
Jürgen Strobel

9

Kiểm tra kích thước của tệp đầu vào, sau đó xuất bất kỳ số nào quá lớn để được biểu thị bằng một tệp có kích thước đó. Đây có vẻ như là một mánh khóe rẻ tiền, nhưng nó là một giải pháp sáng tạo cho một vấn đề phỏng vấn, nó gọn gàng khắc phục vấn đề bộ nhớ và về mặt kỹ thuật là O (n).

void maxNum(ulong filesize)
{
    ulong bitcount = filesize * 8; //number of bits in file

    for (ulong i = 0; i < bitcount; i++)
    {
        Console.Write(9);
    }
}

Nên in 10 bitcount - 1 , sẽ luôn lớn hơn 2 bitcount . Về mặt kỹ thuật, số bạn phải đánh bại là 2 bitcount - (4 * 10 9 - 1) , vì bạn biết có (4 tỷ - 1) số nguyên khác trong tệp và ngay cả khi nén hoàn hảo, chúng sẽ chiếm ít nhất mỗi người một bit.


Tại sao không chỉ Console.Write( 1 << bitcount )thay vì vòng lặp? Nếu có n bit trong tệp, thì bất kỳ số bit (_n_ + 1) nào có số 1 đứng đầu đều được đảm bảo tuyệt đối lớn hơn.
Emmet

@Emmet - Điều đó sẽ chỉ gây ra tràn số nguyên, trừ khi tệp nhỏ hơn kích thước của một int (4 byte trong C #). C ++ có thể cho phép bạn sử dụng một cái gì đó lớn hơn, nhưng C # dường như không cho phép bất cứ thứ gì ngoại trừ int 32 bit với <<toán tử. Dù bằng cách nào, trừ khi bạn cuộn loại số nguyên khổng lồ của riêng mình, nó sẽ có kích thước tệp rất nhỏ. Bản trình diễn: rextester.com/BLETJ59067
Justin Morgan

8
  • Cách tiếp cận đơn giản nhất là tìm số lượng tối thiểu trong tệp và trả về 1 ít hơn số đó. Điều này sử dụng lưu trữ O (1) và thời gian O (n) cho một tệp gồm n số. Tuy nhiên, nó sẽ thất bại nếu phạm vi số bị giới hạn, điều này có thể làm cho min-1 không phải là số.

  • Phương pháp đơn giản và đơn giản để sử dụng bitmap đã được đề cập. Phương pháp đó sử dụng thời gian và lưu trữ O (n).

  • Một phương pháp 2 chuyền với 2 ^ 16 thùng đếm cũng đã được đề cập. Nó đọc các số nguyên 2 * n, vì vậy sử dụng lưu trữ thời gian O (n) và O (1), nhưng nó không thể xử lý các bộ dữ liệu với hơn 2 ^ 16 số. Tuy nhiên, nó dễ dàng được mở rộng thành (ví dụ) 2 ^ 60 số nguyên 64 bit bằng cách chạy 4 lượt thay vì 2 và dễ dàng thích nghi với việc sử dụng bộ nhớ nhỏ bằng cách chỉ sử dụng càng nhiều thùng phù hợp với bộ nhớ và tăng số lần chuyển tương ứng, trong trường hợp nào thời gian chạy không còn là O (n) mà thay vào đó là O (n * log n).

  • Phương pháp XOR'ing tất cả các số với nhau, được đề cập cho đến nay bởi rfrankel và theo chiều dài của ircmaxell trả lời câu hỏi được hỏi trong stackoverflow # 35185 , như ltn100 đã chỉ ra. Nó sử dụng lưu trữ O (1) và thời gian chạy O (n). Nếu hiện tại chúng tôi giả sử số nguyên 32 bit, XOR có xác suất 7% tạo ra một số khác biệt. Đặt vấn đề: đưa ra ~ số 4G khác nhau XOR'd với nhau và ca. 300M không có trong tệp, số bit được đặt ở mỗi vị trí bit có cơ hội là số lẻ hoặc số chẵn. Do đó, 2 ^ 32 số có khả năng phát sinh như nhau khi kết quả XOR, trong đó 93% đã có trong tệp. Lưu ý rằng nếu các số trong tệp không hoàn toàn khác biệt, xác suất thành công của phương thức XOR sẽ tăng.


7

Vì một số lý do, ngay khi đọc vấn đề này, tôi đã nghĩ đến việc chéo hóa. Tôi giả sử số nguyên lớn tùy ý.

Đọc số đầu tiên. Đệm trái với bit 0 cho đến khi bạn có 4 tỷ bit. Nếu bit đầu tiên (thứ tự cao) là 0, đầu ra 1; đầu ra khác 0. (Bạn không thực sự phải đệm trái: bạn chỉ xuất 1 nếu không có đủ bit trong số.) Thực hiện tương tự với số thứ hai, ngoại trừ sử dụng bit thứ hai. Tiếp tục thông qua các tập tin theo cách này. Bạn sẽ xuất ra số bit 4 tỷ một bit một lần và con số đó sẽ không giống với bất kỳ tệp nào trong tệp. Bằng chứng: nó giống như số thứ n, sau đó họ sẽ đồng ý với bit thứ n, nhưng họ không xây dựng.


+1 cho sự sáng tạo (và đầu ra trong trường hợp xấu nhất nhỏ nhất cho giải pháp vượt qua một lần).
hmakholm còn lại trên Monica

Nhưng không có 4 tỷ bit để chéo, chỉ có 32. Bạn sẽ chỉ có một số 32 bit khác với 32 số đầu tiên trong danh sách.
Brian Gordon

@Henning Nó hầu như không vượt qua một lần; bạn vẫn phải chuyển đổi từ unary sang nhị phân. Chỉnh sửa: Vâng tôi đoán đó là một trong những tập tin. Đừng bận tâm.
Brian Gordon

@Brian, ở đâu có cái gì đó "unary" ở đây? Câu trả lời là xây dựng một câu trả lời nhị phân một bit một lần và nó chỉ đọc tệp đầu vào một lần, làm cho nó vượt qua một lần. (Nếu đầu ra thập phân là bắt buộc, mọi thứ sẽ gặp vấn đề - thì có lẽ bạn nên xây dựng một chữ số thập phân trên ba số đầu vào và chấp nhận tăng 10% trong nhật ký của số đầu ra).
hmakholm còn lại trên Monica

2
@Henning Vấn đề không có ý nghĩa đối với các số nguyên lớn tùy ý bởi vì, như nhiều người đã chỉ ra, thật đơn giản khi chỉ tìm số lớn nhất và thêm một hoặc tạo một số rất dài từ chính tệp. Giải pháp đường chéo này đặc biệt không phù hợp vì thay vì phân nhánh trên ibit thứ bạn chỉ có thể xuất 1 bit 4 tỷ lần và ném thêm 1 vào cuối. Tôi ổn với việc có các số nguyên lớn tùy ý trong thuật toán nhưng tôi nghĩ vấn đề là xuất ra một số nguyên 32 bit bị thiếu. Nó không có nghĩa gì khác.
Brian Gordon

6

Bạn có thể sử dụng cờ bit để đánh dấu xem một số nguyên có mặt hay không.

Sau khi duyệt toàn bộ tệp, quét từng bit để xác định xem số đó có tồn tại hay không.

Giả sử mỗi số nguyên là 32 bit, chúng sẽ thuận tiện phù hợp với 1 GB RAM nếu việc gắn cờ bit được thực hiện.


0,5 Gb, trừ khi bạn đã xác định lại byte thành 4 bit ;-)
dty

2
@dty Tôi nghĩ anh ấy có nghĩa là "thoải mái", vì trong đó sẽ có rất nhiều phòng trong 1Gb.
corsiKa

6

Tách khoảng trắng và các ký tự không phải số từ tệp và nối thêm 1. Tệp của bạn hiện chứa một số duy nhất không được liệt kê trong tệp gốc.

Từ Reddit bởi Carbonetc.


Yêu nó! Mặc dù đó không hoàn toàn là câu trả lời mà anh ta đang tìm kiếm ...: D
Johann du Toit

6

Đây là một giải pháp rất đơn giản, rất có thể sẽ mất nhiều thời gian để chạy, nhưng sử dụng rất ít bộ nhớ.

Đặt tất cả các số nguyên có thể là phạm vi từ int_minđến int_maxbool isNotInFile(integer)một hàm trả về true nếu tệp không chứa một số nguyên nhất định và sai khác (bằng cách so sánh số nguyên nhất định đó với mỗi số nguyên trong tệp)

for (integer i = int_min; i <= int_max; ++i)
{
    if (isNotInFile(i)) {
        return i;
    }
}

Câu hỏi chính xác là về thuật toán cho isNotInFilehàm. Hãy chắc chắn rằng bạn hiểu câu hỏi trước khi trả lời.
Aleks G

2
không, câu hỏi là "số nguyên nào không có trong tệp", không phải "là số nguyên x trong tệp". một hàm để xác định câu trả lời cho câu hỏi sau chẳng hạn, chỉ có thể so sánh mọi số nguyên trong tệp với số nguyên trong câu hỏi và trả về true trên một kết quả khớp.
DEG

Tôi nghĩ rằng đây là một câu trả lời hợp pháp. Ngoại trừ I / O, bạn chỉ cần một số nguyên và cờ bool.
Brian Gordon

@Aleks G - Tôi không thấy lý do tại sao điều này được đánh dấu là sai. Tất cả chúng ta đều đồng ý rằng đó là thuật toán chậm nhất trong tất cả :-), nhưng nó hoạt động và chỉ cần 4 byte để đọc tệp. Câu hỏi ban đầu không quy định tập tin chỉ có thể được đọc một lần chẳng hạn.
Simon Mourier

1
@Aleks G - Phải. Tôi chưa bao giờ nói bạn nói điều đó. Chúng tôi chỉ nói IsNotInFile có thể được triển khai một cách tầm thường bằng cách sử dụng một vòng lặp trên tệp: Open; While Not Eof {Read Integer; Return false nếu Integer = i; Else Tiếp tục;}. Nó chỉ cần 4 byte bộ nhớ.
Simon Mourier

5

Đối với ràng buộc bộ nhớ 10 MB:

  1. Chuyển đổi số thành biểu diễn nhị phân của nó.
  2. Tạo một cây nhị phân trong đó left = 0 và right = 1.
  3. Chèn mỗi số trong cây bằng cách sử dụng biểu diễn nhị phân của nó.
  4. Nếu một số đã được chèn, các lá sẽ được tạo.

Khi kết thúc, chỉ cần lấy một đường dẫn chưa được tạo trước đó để tạo số được yêu cầu.

4 tỷ số = 2 ^ 32, nghĩa là 10 MB có thể không đủ.

BIÊN TẬP

Tối ưu hóa là có thể, nếu hai đầu lá đã được tạo và có một cha mẹ chung, thì chúng có thể được gỡ bỏ và cha mẹ được gắn cờ là không phải là một giải pháp. Điều này cắt các nhánh và giảm nhu cầu bộ nhớ.

EDIT II

Không cần phải xây dựng cây hoàn toàn quá. Bạn chỉ cần xây dựng các nhánh sâu nếu số lượng tương tự nhau. Nếu chúng ta cắt cành quá, thì giải pháp này thực tế có thể hoạt động.


6
... và làm thế nào nó sẽ phù hợp với 10 MB?
hmakholm còn lại của Monica

Cách thực hiện: giới hạn độ sâu của BTree ở mức phù hợp với 10MB; điều này có nghĩa là bạn sẽ có kết quả trong tập {false positive | positive} và bạn có thể lặp lại thông qua đó và sử dụng các kỹ thuật khác tìm giá trị.
Jonathan Dickinson

5

Tôi sẽ trả lời phiên bản 1 GB:

Không có đủ thông tin trong câu hỏi, vì vậy tôi sẽ nêu một số giả định trước:

Số nguyên là 32 bit với phạm vi -2,147,483,648 đến 2,147,483,647.

Mã giả:

var bitArray = new bit[4294967296];  // 0.5 GB, initialized to all 0s.

foreach (var number in file) {
    bitArray[number + 2147483648] = 1;   // Shift all numbers so they start at 0.
}

for (var i = 0; i < 4294967296; i++) {
    if (bitArray[i] == 0) {
        return i - 2147483648;
    }
}

4

Miễn là chúng ta đang thực hiện các câu trả lời sáng tạo, đây là một câu trả lời khác.

Sử dụng chương trình sắp xếp bên ngoài để sắp xếp tệp đầu vào bằng số. Điều này sẽ hoạt động cho bất kỳ số lượng bộ nhớ bạn có thể có (nó sẽ sử dụng lưu trữ tệp nếu cần). Đọc qua tệp đã sắp xếp và xuất số đầu tiên bị thiếu.


3

Loại bỏ bit

Một cách là loại bỏ bit, tuy nhiên điều này có thể không thực sự mang lại kết quả (rất có thể nó sẽ không xảy ra). Mã hóa:

long val = 0xFFFFFFFFFFFFFFFF; // (all bits set)
foreach long fileVal in file
{
    val = val & ~fileVal;
    if (val == 0) error;
}

Đếm bit

Theo dõi số lượng bit; và sử dụng các bit với số lượng ít nhất để tạo ra một giá trị. Một lần nữa điều này không đảm bảo tạo ra một giá trị chính xác.

Phạm vi logic

Theo dõi danh sách các phạm vi được sắp xếp (sắp xếp theo thứ tự bắt đầu). Một phạm vi được xác định bởi cấu trúc:

struct Range
{
  long Start, End; // Inclusive.
}
Range startRange = new Range { Start = 0x0, End = 0xFFFFFFFFFFFFFFFF };

Đi qua từng giá trị trong tệp và thử và xóa nó khỏi phạm vi hiện tại. Phương pháp này không có bảo đảm bộ nhớ, nhưng nó sẽ làm khá tốt.


3

2 128 * 10 18 + 1 (đó là (2 8 ) 16 * 10 18 + 1) - nó có thể là một câu trả lời phổ quát cho ngày hôm nay không? Đây là số không thể giữ trong 16 tệp EB, đây là kích thước tệp tối đa trong bất kỳ hệ thống tệp hiện tại nào.


Và làm thế nào bạn sẽ in kết quả? Bạn không thể đặt nó trong một tập tin và việc in trên màn hình sẽ mất vài tỷ năm. Không có thời gian hoạt động có thể đạt được với các máy tính ngày nay.
vsz

người ta không bao giờ nói rằng chúng ta cần in kết quả ở bất cứ đâu, chỉ cần 'tạo' nó. Vì vậy, nó phụ thuộc vào những gì bạn có nghĩa là bằng cách tạo ra. dù sao, câu trả lời của tôi chỉ là một mẹo để tránh làm ra một thuật toán thực sự :)
Michael Sagalovich

3

Tôi nghĩ rằng đây là một vấn đề đã được giải quyết (xem ở trên), nhưng có một trường hợp phụ thú vị cần ghi nhớ bởi vì nó có thể được hỏi:

Nếu có chính xác 4.294.967.295 (2 ^ 32 - 1) số nguyên 32 bit không lặp lại, và do đó chỉ thiếu một, có một giải pháp đơn giản.

Bắt đầu tổng số chạy bằng 0 và với mỗi số nguyên trong tệp, hãy thêm số nguyên đó với tràn 32 bit (có hiệu quả, runningTotal = (runningTotal + nextInteger)% 4294967296). Sau khi hoàn thành, thêm 4294967296/2 vào tổng số đang chạy, một lần nữa với tràn 32 bit. Trừ đi số này từ 4294967296 và kết quả là số nguyên bị thiếu.

Vấn đề "chỉ có một số nguyên bị thiếu" có thể giải quyết được chỉ với một lần chạy và chỉ có 64 bit RAM dành riêng cho dữ liệu (32 cho tổng số đang chạy, 32 để đọc số nguyên tiếp theo).

Hệ quả: Đặc tả chung hơn là cực kỳ đơn giản để phù hợp nếu chúng ta không quan tâm đến kết quả số nguyên phải có bao nhiêu bit. Chúng tôi chỉ tạo ra một số nguyên đủ lớn mà nó không thể chứa trong tệp chúng tôi đưa ra. Một lần nữa, điều này chiếm RAM hoàn toàn tối thiểu. Xem mã giả.

# Grab the file size
fseek(fp, 0L, SEEK_END);
sz = ftell(fp);
# Print a '2' for every bit of the file.
for (c=0; c<sz; c++) {
  for (b=0; b<4; b++) {
    print "2";
  }
}

@Nakilon và TheDayTurns đã chỉ ra điều này trong các bình luận cho câu hỏi ban đầu
Brian Gordon

3

Như Ryan đã nói về cơ bản, sắp xếp tệp và sau đó đi qua các số nguyên và khi một giá trị được bỏ qua ở đó bạn có nó :)

EDIT tại downvoters: OP đã đề cập rằng tệp có thể được sắp xếp để đây là một phương pháp hợp lệ.


Một phần quan trọng là bạn nên làm điều đó khi bạn đi, theo cách đó bạn chỉ phải đọc một lần. Truy cập bộ nhớ vật lý là chậm.
Ryan Amos

@ryan sắp xếp bên ngoài trong hầu hết các trường hợp là một loại hợp nhất để trong lần hợp nhất cuối cùng, bạn có thể thực hiện kiểm tra :)
ratchet freak

Nếu dữ liệu trên đĩa, nó sẽ phải được tải vào bộ nhớ. Điều này xảy ra tự động bởi hệ thống tập tin. Nếu chúng ta phải tìm một số (vấn đề không nêu khác) thì đọc tệp được sắp xếp một dòng tại một thời điểm là phương pháp hiệu quả nhất. Nó sử dụng ít bộ nhớ và không chậm hơn bất cứ thứ gì khác - tệp phải được đọc.
Tony Enni

Bạn sẽ sắp xếp 4 tỷ số nguyên như thế nào khi bạn chỉ có 1 GB bộ nhớ? Nếu bạn sử dụng bộ nhớ virtyual, sẽ mất một thời gian loooong vì các khối bộ nhớ được phân trang vào và ra khỏi bộ nhớ vật lý.
Klas Lindbäck

4
@klas merge sort được thiết kế cho điều đó
ratchet freak

2

Nếu bạn không giả sử ràng buộc 32 bit, chỉ cần trả về số 64 bit được tạo ngẫu nhiên (hoặc 128 bit nếu bạn là người bi quan). Cơ hội va chạm là 1 in 2^64/(4*10^9) = 4611686018.4(khoảng 1 trên 4 tỷ đồng). Bạn sẽ đúng hầu hết thời gian!

(Đùa ... loại.)


Tôi thấy điều này đã được đề xuất :) upvote cho những người đó
Peter Gibson

Nghịch lý sinh nhật làm cho loại giải pháp này không đáng để mạo hiểm, mà không kiểm tra tệp để xem liệu dự đoán ngẫu nhiên của bạn có thực sự là một câu trả lời hợp lệ hay không. (Nghịch lý sinh nhật không áp dụng trong trường hợp này, nhưng liên tục gọi hàm này để tạo ra các giá trị duy nhất mới tạo ra tình huống nghịch lý sinh nhật.)
Peter Cordes

@PeterCordes Các số 128bit được tạo ngẫu nhiên chính xác là cách UUID hoạt động - họ thậm chí còn đề cập đến nghịch lý sinh nhật khi tính xác suất xảy ra va chạm trong trang Wikipedia UUID
Peter Gibson

Biến thể: Tìm tối đa trong bộ, thêm 1.
Phil

Tôi sẽ nhanh chóng sắp xếp lại mảng ban đầu (không có bộ nhớ bổ sung.) Sau đó di chuyển qua mảng và báo cáo số nguyên 'bỏ qua' đầu tiên. Làm xong. Trả lời câu hỏi.
Cấp 42
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.