Làm thế nào để tìm 5 giá trị lặp lại trong thời gian O (n)?


15

Giả sử bạn có một mảng có kích thước chứa các số nguyên từ đến , bao gồm, với chính xác năm lần lặp lại. Tôi cần đề xuất một thuật toán có thể tìm thấy các số lặp lại trong thời gian . Tôi không thể, vì cuộc sống của tôi, nghĩ về bất cứ điều gì. Tôi nghĩ sắp xếp, tốt nhất, sẽ là ? Sau đó di chuyển ngang mảng sẽ là , dẫn đến . Tuy nhiên, tôi không thực sự chắc chắn liệu việc sắp xếp có cần thiết hay không vì tôi đã thấy một số thứ khó khăn với danh sách được liên kết, hàng đợi, ngăn xếp, v.v.1 n - 5 O ( n ) O ( n log n ) O ( n ) O ( n 2 log n )n61n5O(n)O(nlogn)O(n)O(n2logn)


16
O(nlogn)+O(n) không phải là . Đó là . Sẽ là nếu bạn thực hiện sắp xếp n lần. O ( n log n ) O ( n 2 log n )O(n2logn)O(nlogn)O(n2logn)
Vụ kiện của Quỹ Monica


1
@leftaroundabout Các thuật toán này là trong đó là kích thước của mảng và là kích thước của tập hợp đầu vào. vì các thuật toán này hoạt động trongn k k = n - c o n s t a n t O ( n 2 )O(kn)nkk=nconstantO(n2)
Roman Gräf

4
@ RomanGräf nó xuất hiện tình huống thực tế là thế này: các thuật toán hoạt động trong , trong đó là kích thước của miền. Vì vậy, đối với một vấn đề như OP, vấn đề là bạn sử dụng thuật toán như vậy trên miền -ized hoặc thuật toán truyền thống trên miền có kích thước không giới hạn. Làm cho ý nghĩa, quá. k n O ( n log n )O(logkn)knO(nlogn)
leftaroundabout

5
Với , số cho phép duy nhất là , theo mô tả của bạn. Nhưng sau đó sẽ phải lặp lại sáu, không phải năm, lần. 1 1n=611
Alex Reinking

Câu trả lời:


22

Bạn có thể tạo một mảng bổ sung có kích thước . Ban đầu đặt tất cả các phần tử của mảng thành . Sau đó lặp qua mảng đầu vào và tăng thêm 1 cho mỗi . Sau đó, bạn chỉ cần kiểm tra mảng : loop trên và nếu thì được lặp lại. Bạn giải quyết nó trong thời gian với chi phí bộ nhớ là và bởi vì số nguyên của bạn nằm trong khoảng từ đến .n 0 A B [ A [ i ] ] i B A B [ A [ i ] ] > 1 A [ i ] O ( n ) O ( n ) 1 n - 5Bn0AB[A[i]]iBAB[A[i]]>1A[i]O(n)O(n)1n5


26

Giải pháp trong câu trả lời của fade2black là giải pháp tiêu chuẩn, nhưng nó sử dụng không gian . Bạn có thể cải thiện không gian này thành không gian như sau:O ( 1 )O(n)O(1)

  1. Đặt mảng là . Với , tính toán .d = 1 , ... , 5 σ d = Σ n i = 1 A [ i ] dA[1],,A[n]d=1,,5σd=i=1nA[i]d
  2. Tính toán (bạn có thể sử dụng các công thức nổi tiếng để tính tổng sau trong ). Lưu ý rằng , trong đó là các số lặp lại. O ( 1 ) τ d = m d 1 + + m d 5 m 1 , Câu , m 5τd=σdi=1n5idO(1)τd=m1d++m5dm1,,m5
  3. Tính đa thức . Các hệ số của đa thức này là các hàm đối xứng của có thể được tính từ trong .m 1 , ... , m 5 τ 1 , ... , τ 5 O ( 1 )P(t)=(tm1)(tm5)m1,,m5τ1,,τ5O(1)
  4. Tìm tất cả các gốc của đa thức bằng cách thử tất cả các khả năng .n - 5P(t)n5

Thuật toán này giả định mô hình máy RAM, trong đó các phép toán số học cơ bản trên các từ có tỷ lệ mất thời gian .O ( 1 )O(logn)O(1)


Một cách khác để xây dựng giải pháp này là dọc theo các dòng sau:

  1. Tính và suy ra bằng công thức .y 1 = m 1 + + m 5 y 1 = x 1 - n - 5 i = 1 ix1=i=1nA[i]y1=m1++m5y1=x1i=1n5i
  2. Tính trong bằng công thức Ox2=1i<jA[i]A[j]x 2 = ( A [ 1 ] ) Một [ 2 ] + ( A [ 1 ] + Một [ 2 ] ) Một [ 3 ] + ( A [ 1 ] + A [ 2O(n)
    x2=(A[1])A[2]+(A[1]+A[2])A[3]+(A[1]+A[2]+A[3])A[4]++(A[1]++A[n1])A[n].
  3. Suy sử dụng công thức y 2 = x 2y2=1i<j5mimj
    y2=x21i<jn5ij(i=1n5i)y1.
  4. Tính và suy ra dọc theo các dòng tương tự.y 3 , y 4 , y 5x3,x4,x5y3,y4,y5
  5. Các giá trị của là (tối đa ký) các hệ số của đa thức từ giải pháp trước. P ( t )y1,,y5P(t)

Giải pháp này cho thấy rằng nếu chúng ta thay thế 5 bằng , thì chúng ta sẽ nhận được (tôi tin) thuật toán bằng cách sử dụng không gian , thực hiện các phép toán số học trên các số nguyên có độ dài bit , giữ tối đa trong số này tại bất kỳ thời điểm nào. (Điều này đòi hỏi phải phân tích cẩn thận các phép nhân mà chúng tôi thực hiện, hầu hết chỉ liên quan đến một toán hạng có độ dài .) Có thể hiểu rằng điều này có thể được cải thiện theo thời gian và không gian bằng mô-đun Môn số học.O ( d 2 n ) O ( d 2 ) O ( d n ) O ( d log n ) O ( d ) O ( log n ) O ( d n ) O ( d )dO(d2n)O(d2)O(dn)O(dlogn)O(d)O(logn)O(dn)O(d)


Bất kỳ giải thích nào về và , , , v.v. Tại sao ? τ d P ( t ) m i d { 1 , 2 , 3 , 4 , 5 }σdτdP(t)mid{1,2,3,4,5}
bay xốp

3
Cái nhìn sâu sắc đằng sau giải pháp là thủ thuật tổng hợp , xuất hiện trong nhiều bài tập (ví dụ: làm thế nào để bạn tìm thấy phần tử bị thiếu từ một mảng có độ dài chứa tất cả trừ một trong các số ?). Thủ thuật tính tổng có thể được sử dụng để tính cho hàm tùy ý và câu hỏi là chọn để có thể suy ra . Câu trả lời của tôi sử dụng các thủ thuật quen thuộc từ lý thuyết cơ bản về các hàm đối xứng. 1 , Bắn , n f ( m 1 ) + + f ( m 5 ) f f m 1 , Bắn , m 5n11,,nf(m1)++f(m5)ffm1,,m5
Yuval Filmus

1
@hoffmale Thật ra, . O(d2)
Yuval Filmus

1
@hoffmale Mỗi trong số họ mất từ máy. d
Yuval Filmus

1
@BurnsBA Vấn đề với cách tiếp cận này là lớn hơn nhiều so với . Hoạt động trên số lượng lớn là chậm hơn. ( n - 4 ) ( n - 5 )(n5)#(n4)(n5)2
Yuval Filmus

8

Ngoài ra còn có thuật toán không gian tuyến tính và không gian tuyến tính dựa trên phân vùng, có thể linh hoạt hơn nếu bạn đang cố gắng áp dụng điều này cho các biến thể của vấn đề mà phương pháp toán học không hoạt động tốt. Điều này đòi hỏi phải thay đổi mảng cơ bản và có các yếu tố không đổi tồi tệ hơn so với phương pháp toán học. Cụ thể hơn, tôi tin rằng chi phí tính theo tổng số giá trị và số lượng trùng lặp là và , mặc dù việc chứng minh nó sẽ nghiêm ngặt mất nhiều thời gian hơn tôi có tại thời điểm này.d O ( n log d ) O ( d )ndO(nlogd)O(d)


Thuật toán

Bắt đầu với một danh sách các cặp, trong đó cặp đầu tiên là phạm vi trên toàn bộ mảng hoặc nếu 1 chỉ mục.[(1,n)]

Lặp lại các bước sau cho đến khi danh sách trống:

  1. Lấy và xóa bất kỳ cặp khỏi danh sách.(i,j)
  2. Tìm mức tối thiểu và tối đa, và , của phân đoạn được ký hiệu.maxminmax
  3. Nếu , phân đoạn con chỉ bao gồm các phần tử bằng nhau. Mang lại các yếu tố của nó ngoại trừ một và bỏ qua các bước 4 đến 6.min=max
  4. Nếu , phân đoạn con không chứa các bản sao. Bỏ qua bước 5 và 6.maxmin=ji
  5. Phân vùng phân đoạn xung quanh , sao cho các phần tử lên đến một số chỉ mục nhỏ hơn dấu phân cách và các phần tử bên trên chỉ mục đó thì không. kmin+max2k
  6. Thêm và vào danh sách.( k + 1 , j )(i,k)(k+1,j)

Phân tích chữ thảo về độ phức tạp thời gian.

Các bước từ 1 đến 6 mất thời gian , vì việc tìm tối thiểu và tối đa và phân vùng có thể được thực hiện trong thời gian tuyến tính.O(ji)

Mỗi cặp trong danh sách là cặp đầu tiên, hoặc là con của một số cặp mà phân đoạn tương ứng chứa một phần tử trùng lặp. Có nhiều nhất là như vậy, vì mỗi lần truyền qua một nửa phạm vi có thể trùng lặp, do đó, có tối đa khi bao gồm các cặp không có bản sao. Tại bất kỳ thời điểm nào, kích thước của danh sách không quá .( 1 , n ) d log 2 n + 1 2 d log 2 n + 1 2 d(i,j)(1,n)dlog2n+12dlog2n+12d

Hãy xem xét công việc để tìm bất kỳ một bản sao. Điều này bao gồm một chuỗi các cặp trong phạm vi giảm theo cấp số nhân, do đó, tổng công việc là tổng của chuỗi hình học, hoặc . Điều này tạo ra một hệ quả rõ ràng rằng tổng công việc cho các bản sao phải là , là tuyến tính tính theo .d O ( n d ) nO(n)dO(nd)n

Để tìm một ràng buộc chặt chẽ hơn, hãy xem xét trường hợp xấu nhất của các bản sao trải rộng tối đa. Theo trực giác, tìm kiếm có hai giai đoạn, một giai đoạn trong đó toàn bộ mảng được duyệt qua mỗi lần, trong các phần nhỏ dần và một trong đó các phần nhỏ hơn nên chỉ các phần của mảng được dịch chuyển. Giai đoạn đầu tiên chỉ có thể là sâu, vì vậy có chi phí và giai đoạn thứ hai có chi phí vì tổng diện tích được tìm kiếm lại giảm theo cấp số nhân . logdO(nlogd)O(n)ndlogdO(nlogd)O(n)


Cám ơn vì đã giải thích. Bây giờ tôi hiểu rồi. Một thuật toán rất đẹp!
DW

5

Để lại điều này như một câu trả lời vì nó cần nhiều không gian hơn một bình luận đưa ra.

O(nlogn)O(n2logn)O(f)O(g)O(f+g)=O(maxf,g)

Để nhân độ phức tạp thời gian, bạn cần sử dụng vòng lặp for. Nếu bạn có một vòng lặp có độ dài và với mỗi giá trị trong vòng lặp bạn thực hiện một hàm lấy , thì bạn sẽ nhận được thời gian .O ( g ) O ( f g )fO(g)O(fg)

Vì vậy, trong trường hợp của bạn, bạn sắp xếp theo và sau đó chuyển sang dẫn đến . Nếu với mỗi so sánh của thuật toán sắp xếp bạn phải thực hiện một phép tính lấy , thì nó sẽ lấy nhưng đó không phải là trường hợp ở đây.O ( n ) O ( n log n + n ) = O ( n log n ) O ( n ) O ( n 2 log n )O(nlogn)O(n)O(nlogn+n)=O(nlogn)O(n)O(n2logn)


Trong trường hợp bạn tò mò về tuyên bố của tôi rằng , điều quan trọng cần lưu ý là điều đó không phải lúc nào cũng đúng. Nhưng nếu hoặc (giữ cho một loạt các hàm chung), nó sẽ giữ. Thời gian phổ biến nhất mà nó không giữ là khi các tham số bổ sung được tham gia và bạn nhận được các biểu thức như .O(f+g)=O(maxf,g)fO(g)gO(f)O(2cn+nlogn)


3

Có một biến thể tại chỗ rõ ràng của kỹ thuật mảng boolean bằng cách sử dụng thứ tự của các phần tử là cửa hàng (trong đó arr[x] == xcho các phần tử "tìm thấy"). Không giống như biến thể phân vùng có thể được chứng minh là chung chung hơn, tôi không chắc chắn khi bạn thực sự cần một cái gì đó như thế này, nhưng nó đơn giản.

for idx from n-4 to n
    while arr[arr[idx]] != arr[idx]
        swap(arr[arr[idx]], arr[idx])

Điều này chỉ liên tục đặt arr[idx]tại vị trí arr[idx]cho đến khi bạn tìm thấy vị trí đó đã được thực hiện, tại thời điểm đó nó phải là một bản sao. Lưu ý rằng tổng số lần hoán đổi được giới hạn bởi vì mỗi lần hoán đổi làm cho điều kiện thoát của nó đúng.n


Bạn sẽ phải đưa ra một số loại lập luận rằng whilevòng lặp bên trong chạy trong thời gian không đổi trung bình. Mặt khác, đây không phải là thuật toán thời gian tuyến tính.
David Richerby

@DavidR Richby Nó không chạy trung bình thời gian liên tục, nhưng vòng ngoài chỉ chạy 5 lần nên không sao. Lưu ý rằng tổng số lần hoán đổi được giới hạn bởi vì mỗi lần hoán đổi làm cho điều kiện thoát của nó đúng, vì vậy ngay cả khi số lượng giá trị trùng lặp tăng tổng thời gian vẫn là tuyến tính (còn gọi là bước thay vì ). nnnd
Veedrac

Rất tiếc, bằng cách nào đó tôi đã không nhận thấy rằng vòng lặp bên ngoài chạy một số lần không đổi! (Được chỉnh sửa để bao gồm ghi chú của bạn về số lần hoán đổi và cũng vì vậy tôi có thể đảo ngược downvote của mình.)
David Richerby

1

Trừ các giá trị bạn có từ tổng .i=1ni=(n1)n2

Vì vậy, sau thời gian (giả sử số học là O (1), thực tế không phải vậy, nhưng hãy giả vờ) bạn có tổng σ 1 trong 5 số nguyên từ 1 đến n:Θ(n)σ1

x1+x2+x3+x4+x5=σ1

Giả sử, điều này là không tốt, phải không? Bạn không thể tìm ra cách chia số này thành 5 số riêng biệt.

Ah, nhưng đây là nơi để trở nên vui vẻ! Bây giờ làm tương tự như trước đây, nhưng trừ bình phương của các giá trị từ . Bây giờ bạn có:i=1ni2

x12+x22+x32+x42+x52=σ2

Xem tôi đang đi đâu với điều này? Thực hiện tương tự cho các lũy thừa 3, 4 và 5 và bạn có cho mình 5 phương trình độc lập trong 5 biến. Tôi khá chắc chắn rằng bạn có thể giải quyết cho .x

Hãy cẩn thận: Số học không thực sự là O (1). Ngoài ra, bạn cần một chút không gian để thể hiện số tiền của bạn; nhưng không nhiều như bạn tưởng tượng - bạn có thể làm hầu hết tất cả mọi thứ theo mô đun, miễn là bạn có, oh, bit; nên làm vậy.log(5n6)


Không @YuvalFilmus đề xuất giải pháp tương tự?
fade2black

@ fade2black: Ồ, vâng, có, xin lỗi, tôi chỉ thấy dòng đầu tiên của giải pháp của mình.
einpoklum

0

Cách dễ nhất để giải quyết vấn đề là tạo mảng, trong đó chúng ta sẽ đếm số lần xuất hiện của từng số trong mảng ban đầu, sau đó duyệt qua tất cả các số từ đến n - 5 và kiểm tra xem số đó có xuất hiện nhiều lần không, độ phức tạp của điều này giải pháp trong cả bộ nhớ và thời gian là tuyến tính, hoặc O ( N )1n5O(N)


1
Đây là câu trả lời tương tự của @ fade2black (mặc dù dễ nhìn hơn một chút)
LangeHaare

0

Ánh xạ một mảng đến 1 << A[i]và sau đó XOR mọi thứ với nhau. Các bản sao của bạn sẽ là các số tắt bit tương ứng.


Có năm bản sao, vì vậy thủ thuật xor sẽ không bị hỏng trong một số trường hợp.
Ác

1
Thời gian chạy của cái này là . Mỗi bitvector dài n bit, do đó, mỗi thao tác bitvector mất thời gian O ( n ) và bạn thực hiện một thao tác vectơ bit cho mỗi phần tử của mảng ban đầu, trong tổng thời gian O ( n 2 ) . O(n2)nO(n)O(n2)
DW

@DW Nhưng do các máy mà chúng ta thường sử dụng được cố định ở mức 32 hoặc 64 bit và chúng không thay đổi trong thời gian chạy (tức là chúng không đổi), tại sao chúng không được xử lý như vậy và cho rằng các hoạt động bit nằm trong thay vì O ( n ) ? O(1)O(n)
code_dredd

1
@ray, tôi nghĩ bạn đã trả lời câu hỏi của riêng bạn. Cho rằng các máy mà chúng ta thường sử dụng được cố định ở 64 bit, thời gian chạy để thực hiện thao tác trên vectơ -bit là O ( n ) , không phải O ( 1 ) . Phải có một số thứ giống như hướng dẫn n / 64 để thực hiện một số thao tác trên tất cả n bit của vectơ n -bit và n / 64O ( n ) , không phải O ( 1 ) . nO(n)O(1)n/64nnn/64O(n)O(1)
DW

@DW Những gì tôi đã ra khỏi trước. nhận xét là một vectơ bit được đề cập đến một phần tử trong một mảng có kích thước , với vectơ bit là 64 bit, đây sẽ là hằng số mà tôi đề cập đến. Rõ ràng, việc xử lý một mảng có kích thước n sẽ mất thời gian O ( k n ) , nếu chúng ta giả sử có k- bit trên mỗi phần tử và n số phần tử trong mảng. Nhưng k = 64 , do đó, một thao tác cho một phần tử mảng có số bit không đổi phải là O ( 1 ) thay vì O ( k ) và mảng OnnO(kn)knk=64O(1)O(k) thay vì O ( k n ) . Bạn đang giữ k vì mục đích hoàn chỉnh / chính xác hay tôi đang thiếu thứ gì khác? O(n)O(kn)k
code_dredd

-2
DATA=[1,2,2,2,2,2]

from collections import defaultdict

collated=defaultdict(list):
for item in DATA:
    collated[item].append(item)
    if len(collated) == 5:
        return item.

# n time

4
Chào mừng đến với trang web. Chúng tôi là một trang web về khoa học máy tính , vì vậy chúng tôi đang tìm kiếm các thuật toán và giải thích, chứ không phải các đoạn mã yêu cầu hiểu một ngôn ngữ cụ thể và các thư viện của nó. Cụ thể, yêu cầu của bạn rằng mã này chạy trong thời gian tuyến tính giả định collated[item].append(item)chạy trong thời gian không đổi. Điều đó có thực sự đúng không?
David Richerby

3
Ngoài ra, bạn đang tìm kiếm một giá trị được lặp lại năm lần. Ngược lại, OP đang tìm kiếm năm giá trị, mỗi giá trị được lặp lại hai lần.
Yuval Filmus
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.