Cập nhật: Thuật toán đầu tiên được mô tả ở đây bị che khuất bởi câu trả lời thứ hai của Armin Rigo , đơn giản và hiệu quả hơn nhiều. Nhưng cả hai phương pháp này đều có một nhược điểm. Họ cần nhiều giờ để tìm ra kết quả cho một triệu số nguyên. Vì vậy, tôi đã thử thêm hai biến thể (xem nửa sau của câu trả lời này) trong đó phạm vi của số nguyên đầu vào được giả định là bị giới hạn. Giới hạn như vậy cho phép các thuật toán nhanh hơn nhiều. Ngoài ra, tôi đã cố gắng tối ưu hóa mã của Armin Rigo. Xem kết quả điểm chuẩn của tôi ở cuối.
Đây là một ý tưởng về thuật toán sử dụng bộ nhớ O (N). Độ phức tạp thời gian là O (N 2 log N), nhưng có thể giảm xuống O (N 2 ).
Thuật toán sử dụng các cấu trúc dữ liệu sau:
prev
: mảng các chỉ mục trỏ đến phần tử trước của dãy con (có thể không đầy đủ).
hash
: hashmap với key = sự khác biệt giữa các cặp liên tiếp trong dãy con và value = hai hashmap khác. Đối với các bản đồ băm khác này: key = chỉ số bắt đầu / kết thúc của dãy con, giá trị = cặp của (độ dài dãy con, chỉ số kết thúc / bắt đầu của dãy con).
pq
: hàng đợi ưu tiên cho tất cả các giá trị "chênh lệch" có thể có cho các chuỗi con được lưu trữ trong prev
và hash
.
Thuật toán:
- Khởi tạo
prev
với các chỉ mục i-1
. Cập nhật hash
và pq
đăng ký tất cả các chuỗi con (không đầy đủ) được tìm thấy ở bước này và "sự khác biệt" của chúng.
- Nhận (và loại bỏ) "sự khác biệt" nhỏ nhất từ
pq
. Nhận bản ghi tương ứng từ hash
và quét một trong các bản đồ băm cấp hai. Tại thời điểm này, tất cả các chuỗi con với "chênh lệch" đã cho đã hoàn tất. Nếu bản đồ băm cấp hai chứa độ dài dãy con tốt hơn so với tìm thấy cho đến nay, hãy cập nhật kết quả tốt nhất.
- Trong mảng
prev
: đối với mỗi phần tử của bất kỳ trình tự nào được tìm thấy ở bước số 2, chỉ mục giảm dần và cập nhật hash
và có thể pq
. Trong khi cập nhật hash
, chúng tôi có thể thực hiện một trong các thao tác sau: thêm một dãy con mới có độ dài 1, hoặc tăng một số dãy con hiện có lên 1 hoặc hợp nhất hai dãy con hiện có.
- Xóa bản ghi bản đồ băm được tìm thấy ở bước # 2.
- Tiếp tục từ bước số 2 khi
pq
không có sản phẩm nào.
Thuật toán này cập nhật O (N) phần tử của prev
O (N) lần mỗi lần. Và mỗi bản cập nhật này có thể yêu cầu thêm một "sự khác biệt" mới vào pq
. Tất cả điều này có nghĩa là độ phức tạp thời gian của O (N 2 log N) nếu chúng ta sử dụng triển khai heap đơn giản cho pq
. Để giảm nó xuống O (N 2 ), chúng tôi có thể sử dụng triển khai hàng đợi ưu tiên nâng cao hơn. Một số khả năng được liệt kê trên trang này: Hàng đợi Ưu tiên .
Xem mã Python tương ứng trên Ideone . Mã này không cho phép các phần tử trùng lặp trong danh sách. Có thể sửa lỗi này, nhưng dù sao cũng sẽ là một cách tối ưu hóa tốt để loại bỏ các bản sao (và tìm chuỗi con dài nhất ngoài các bản sao một cách riêng biệt).
Và mã tương tự sau khi tối ưu hóa một chút . Tại đây, tìm kiếm được kết thúc ngay khi độ dài của dãy con nhân với "sự khác biệt" của dãy con có thể vượt quá phạm vi danh sách nguồn.
Mã của Armin Rigo rất đơn giản và khá hiệu quả. Nhưng trong một số trường hợp, nó thực hiện một số tính toán bổ sung có thể tránh được. Tìm kiếm có thể bị chấm dứt ngay khi độ dài của dãy con nhân với "sự khác biệt" có thể có của dãy con vượt quá phạm vi danh sách nguồn:
def findLESS(A):
Aset = set(A)
lmax = 2
d = 1
minStep = 0
while (lmax - 1) * minStep <= A[-1] - A[0]:
minStep = A[-1] - A[0] + 1
for j, b in enumerate(A):
if j+d < len(A):
a = A[j+d]
step = a - b
minStep = min(minStep, step)
if a + step in Aset and b - step not in Aset:
c = a + step
count = 3
while c + step in Aset:
c += step
count += 1
if count > lmax:
lmax = count
d += 1
return lmax
print(findLESS([1, 4, 5, 7, 8, 12]))
Nếu phạm vi số nguyên trong dữ liệu nguồn (M) nhỏ, một thuật toán đơn giản có thể thực hiện với thời gian O (M 2 ) và không gian O (M):
def findLESS(src):
r = [False for i in range(src[-1]+1)]
for x in src:
r[x] = True
d = 1
best = 1
while best * d < len(r):
for s in range(d):
l = 0
for i in range(s, len(r), d):
if r[i]:
l += 1
best = max(best, l)
else:
l = 0
d += 1
return best
print(findLESS([1, 4, 5, 7, 8, 12]))
Nó tương tự như phương pháp đầu tiên của Armin Rigo, nhưng nó không sử dụng bất kỳ cấu trúc dữ liệu động nào. Tôi cho rằng dữ liệu nguồn không có bản sao. Và (để giữ cho mã đơn giản) Tôi cũng giả sử rằng giá trị đầu vào tối thiểu là không âm và gần bằng không.
Thuật toán trước đây có thể được cải thiện nếu thay vì mảng boolean, chúng ta sử dụng cấu trúc dữ liệu bit và các phép toán bit để xử lý dữ liệu song song. Đoạn mã được hiển thị bên dưới triển khai bitet dưới dạng số nguyên Python được tích hợp sẵn. Nó có các giả định giống nhau: không có bản sao, giá trị đầu vào tối thiểu không âm và gần bằng không. Độ phức tạp thời gian là O (M 2 * log L) trong đó L là độ dài của dãy con tối ưu, độ phức tạp không gian là O (M):
def findLESS(src):
r = 0
for x in src:
r |= 1 << x
d = 1
best = 1
while best * d < src[-1] + 1:
c = best
rr = r
while c & (c-1):
cc = c & -c
rr &= rr >> (cc * d)
c &= c-1
while c != 1:
c = c >> 1
rr &= rr >> (c * d)
rr &= rr >> d
while rr:
rr &= rr >> d
best += 1
d += 1
return best
Điểm chuẩn:
Dữ liệu đầu vào (khoảng 100000 số nguyên) được tạo theo cách này:
random.seed(42)
s = sorted(list(set([random.randint(0,200000) for r in xrange(140000)])))
Và đối với các thuật toán nhanh nhất, tôi cũng sử dụng dữ liệu sau (khoảng 1000000 số nguyên):
s = sorted(list(set([random.randint(0,2000000) for r in xrange(1400000)])))
Tất cả kết quả hiển thị thời gian tính bằng giây:
Size: 100000 1000000
Second answer by Armin Rigo: 634 ?
By Armin Rigo, optimized: 64 >5000
O(M^2) algorithm: 53 2940
O(M^2*L) algorithm: 7 711