Đây là một O(N)
giải pháp đơn giản sử dụng O(N)
không gian. Tôi giả định rằng chúng tôi đang giới hạn danh sách đầu vào ở các số không âm và chúng tôi muốn tìm số không âm đầu tiên không có trong danh sách.
- Tìm độ dài của danh sách; hãy nói rằng nó là
N
.
- Phân bổ một mảng
N
boolean, được khởi tạo cho tất cả false
.
- Đối với mỗi số
X
trong danh sách, nếu X
nhỏ hơn N
, hãy đặt X'th
phần tử của mảng thành true
.
- Quét mảng bắt đầu từ chỉ mục
0
, tìm kiếm phần tử đầu tiên false
. Nếu bạn tìm thấy đầu tiên false
ở chỉ mục I
, thì đó I
là câu trả lời. Nếu không (tức là khi tất cả các phần tử đều là true
) thì câu trả lời là N
.
Trong thực tế, "mảng N
boolean" có thể sẽ được mã hóa dưới dạng "bitmap" hoặc "bitet" được biểu diễn dưới dạng một byte
hoặc int
mảng. Điều này thường sử dụng ít dung lượng hơn (tùy thuộc vào ngôn ngữ lập trình) và cho phép quét lần đầu tiên false
được thực hiện nhanh hơn.
Đây là cách / tại sao thuật toán hoạt động.
Giả sử rằng các N
số trong danh sách không khác biệt, hoặc một hoặc nhiều trong số chúng lớn hơn N
. Điều này có nghĩa là phải có ít nhất một số trong phạm vi 0 .. N - 1
không có trong danh sách. Vì vậy bài toán tìm số bị thiếu nhỏ nhất do đó phải rút gọn thành bài toán tìm số bị thiếu nhỏ nhất nhỏ hơnN
. Điều này có nghĩa là chúng ta không cần phải theo dõi những con số lớn hơn hoặc bằng N
... bởi vì chúng sẽ không phải là câu trả lời.
Thay thế cho đoạn trước là danh sách là một hoán vị của các số từ 0 .. N - 1
. Trong trường hợp này, bước 3 đặt tất cả các phần tử của mảng thành true
và bước 4 cho chúng ta biết rằng số "bị thiếu" đầu tiên là N
.
Độ phức tạp tính toán của thuật toán là O(N)
với một hằng số tương đối nhỏ. Nó làm cho hai tuyến tính đi qua danh sách hoặc chỉ một lần vượt qua nếu độ dài danh sách được biết là bắt đầu bằng. Không cần phải biểu diễn việc lưu giữ toàn bộ danh sách trong bộ nhớ, vì vậy việc sử dụng bộ nhớ tiệm cận của thuật toán chỉ là những gì cần thiết để biểu diễn mảng boolean; tức là O(N)
các bit.
(Ngược lại, các thuật toán dựa vào sắp xếp hoặc phân vùng trong bộ nhớ giả định rằng bạn có thể đại diện cho toàn bộ danh sách trong bộ nhớ. Ở dạng câu hỏi đã được hỏi, điều này sẽ yêu cầu các từ O(N)
64 bit.)
@Jorn nhận xét rằng từ bước 1 đến bước 3 là một biến thể về sắp xếp đếm. Theo một nghĩa nào đó thì anh ấy đúng, nhưng sự khác biệt rất đáng kể:
- Sắp xếp đếm yêu cầu một mảng (ít nhất)
Xmax - Xmin
bộ đếm trong đó Xmax
là số lớn nhất trong danh sách và Xmin
là số nhỏ nhất trong danh sách. Mỗi bộ đếm phải có thể đại diện cho N trạng thái; nghĩa là giả sử một biểu diễn nhị phân, nó phải có kiểu số nguyên (ít nhất) ceiling(log2(N))
bit.
- Để xác định kích thước mảng, một sắp xếp đếm cần thực hiện một lần chuyển đầu tiên qua danh sách để xác định
Xmax
và Xmin
.
- Do đó, yêu cầu không gian tối thiểu trong trường hợp xấu nhất là
ceiling(log2(N)) * (Xmax - Xmin)
bit.
Ngược lại, thuật toán được trình bày ở trên chỉ đơn giản yêu cầu N
các bit trong trường hợp xấu nhất và tốt nhất.
Tuy nhiên, phân tích này dẫn đến trực giác rằng nếu thuật toán thực hiện lần đầu tiên đi qua danh sách để tìm số 0 (và đếm các phần tử trong danh sách nếu được yêu cầu), nó sẽ đưa ra câu trả lời nhanh hơn mà không sử dụng khoảng trắng nào nếu nó tìm thấy số 0. Điều này chắc chắn là đáng làm nếu khả năng cao là bạn tìm thấy ít nhất một số 0 trong danh sách. Và vượt qua này không thay đổi độ phức tạp tổng thể.
CHỈNH SỬA: Tôi đã thay đổi mô tả của thuật toán để sử dụng "mảng boolean" vì mọi người dường như thấy mô tả ban đầu của tôi sử dụng bit và bitmap là khó hiểu.