Dãy con cách đều nhau dài nhất


76

Tôi có một triệu số nguyên theo thứ tự đã sắp xếp và tôi muốn tìm dãy con dài nhất mà hiệu số giữa các cặp liên tiếp bằng nhau. Ví dụ

1, 4, 5, 7, 8, 12

có một dãy con

   4,       8, 12

Phương pháp ngây thơ của tôi là tham lam và chỉ kiểm tra xem bạn có thể mở rộng một dãy con bao xa từ mỗi điểm. Điều này có vẻ cần O(n²)thời gian.

Có cách nào nhanh hơn để giải quyết vấn đề này không?

Cập nhật. Tôi sẽ kiểm tra mã được đưa ra trong các câu trả lời càng sớm càng tốt (cảm ơn bạn). Tuy nhiên, rõ ràng là sử dụng bộ nhớ n ^ 2 sẽ không hoạt động. Cho đến nay không có mã kết thúc với đầu vào là [random.randint(0,100000) for r in xrange(200000)].

Thời gian. Tôi đã thử nghiệm với dữ liệu đầu vào sau trên hệ thống 32 bit của mình.

a= [random.randint(0,10000) for r in xrange(20000)] 
a.sort()
  • Phương pháp lập trình động của ZelluX sử dụng 1,6G RAM và mất 2 phút 14 giây. Với pypy, chỉ mất 9 giây! Tuy nhiên, nó gặp sự cố với lỗi bộ nhớ trên các đầu vào lớn.
  • Phương pháp thời gian O (thứ) của Armin mất 9 giây với pypy nhưng chỉ có 20MB RAM. Tất nhiên điều này sẽ tồi tệ hơn nhiều nếu phạm vi lớn hơn nhiều. Việc sử dụng bộ nhớ thấp có nghĩa là tôi cũng có thể kiểm tra nó với a = [random.randint (0,100000) cho r trong xrange (200000)] nhưng nó không hoàn thành trong vài phút tôi đã đưa nó bằng pypy.

Để có thể kiểm tra phương pháp của Kluev, tôi reran với

a= [random.randint(0,40000) for r in xrange(28000)] 
a = list(set(a))
a.sort()

để tạo một danh sách có độ dài gần đúng 20000. Tất cả thời gian với pypy

  • ZelluX, 9 giây
  • Kluev, 20 giây
  • Armin, 52 giây

Có vẻ như nếu phương pháp ZelluX có thể được tạo ra không gian tuyến tính thì nó sẽ là người chiến thắng rõ ràng.


3
BẠN nghĩ việc này được thực hiện nhanh chóng đến mức nào?
Mitch Wheat

44
Tôi không hiểu tất cả các phiếu phản đối, và đặc biệt không phải là các phiếu bầu sát sao (lạc đề, phù hợp hơn với Super User ? Nghiêm túc đấy? ). Đó là một vấn đề thú vị mà tôi cũng muốn xem câu trả lời. Vì vậy, +1 từ tôi.
Tim Pietzcker

7
@ user2179021: Bạn có thể cải thiện câu hỏi của mình bằng cách thêm mã bạn đã có. Điều đó dường như làm dịu một số người dùng SO quan trọng hơn. Đừng lo lắng về số phiếu phản đối ngay bây giờ.
Tim Pietzcker

3
@TimPietzcker Tôi với bạn ở đây, tôi đã nhìn thấy câu hỏi worser nhiều, tôi nghĩ rằng nó là một tốt ở đây
La Mã Pekar

7
Trong ví dụ, điều gì quyết định đó 4, 8, 12là đầu ra chính xác trên 1, 4, 7đó là một chuỗi dài bằng nhau?
FThompson

Câu trả lời:


11

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:

  1. 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 đủ).
  2. 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).
  3. 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 prevhash.

Thuật toán:

  1. Khởi tạo prevvới các chỉ mục i-1. Cập nhật hashpqđă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.
  2. 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ừ hashvà 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.
  3. 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 hashvà 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ó.
  4. Xóa bản ghi bản đồ băm được tìm thấy ở bước # 2.
  5. Tiếp tục từ bước số 2 khi pqkhô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 prevO (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).

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

2
Tôi phải thừa nhận rằng tôi không hiểu giải pháp này. Bạn có thể cung cấp một số mã?
eleanora

@ user2179021: Không chắc nó sẽ hữu ích. Đó sẽ là khá nhiều mã. Không dễ viết. Quá phức tạp để hiểu. Tôi vẫn hy vọng có thể lấy ý tưởng từ đoạn mô tả ngắn này. Có thể bạn có câu hỏi về một số bước cụ thể?
Evgeny Kluev

Ngay cả bước 1 cũng sẽ hữu ích. Chúng ta đặt trước, tiếp theo, băm và pq là gì trong bước đầu tiên bằng cách sử dụng ví dụ 1, 4, 5, 7, 8, 12 trong câu hỏi?
eleanora,

@ user2179021: previs [Nil, 0,1,2,3,4]. nextlà [1,2,3,4,5, Nil]. pqsẽ chứa tất cả các khác biệt liền kề: 1,2,3,4. Mọi dãy con đều có độ dài 1. Giá trị này cùng với các vị trí bắt đầu / kết thúc được lưu trữ trong hash: {1: ({1: 1,3: 1}, {2: 1,4: 1}), 2: ({2: 1 }, {3: 1}), 3: ({0: 1}, {1: 1}), 4: ({4: 1}, {5: 1})}.
Evgeny Kluev

1
@EvgenyKluev Cách tiếp cận của bạn ở đây tốt hơn theo ít nhất ba cách :) 1. Giải pháp của bạn được song song hóa để làm cho nó thực sự nhanh chóng. 2. Độ phức tạp về thời gian là rõ ràng và không phụ thuộc vào kích thước của các con số. Tôi không chắc 100 phần trăm về độ phức tạp về thời gian của giải pháp được đề xuất cho câu hỏi được liên kết. 3. Nếu bạn có một số ý tưởng về phạm vi khả dĩ của 'd', giải pháp của bạn thậm chí còn nhanh hơn.
eleanora

19

Chúng tôi có thể có giải pháp O(n*m)kịp thời với nhu cầu bộ nhớ rất ít, bằng cách điều chỉnh của bạn. Đây nlà số lượng mục trong dãy số đầu vào đã cho và mlà phạm vi, tức là số cao nhất trừ đi số thấp nhất.

Gọi A là dãy tất cả các số đầu vào (và sử dụng tính toán trước set()để trả lời câu hỏi "số này có trong A không?"). Gọi d là bước của dãy con mà chúng ta đang tìm (hiệu số giữa hai số của dãy con này). Với mọi giá trị có thể có của d, hãy thực hiện quét tuyến tính sau trên tất cả các số đầu vào: với mọi số n từ A theo thứ tự tăng dần, nếu số đó chưa được nhìn thấy, hãy nhìn về phía A để biết độ dài của dãy bắt đầu từ n với a bước d. Sau đó đánh dấu tất cả các mục trong trình tự đó là đã thấy, để chúng ta tránh tìm kiếm lại từ chúng, cho cùng một d. Do đó, độ phức tạp chỉ O(n)dành cho mọi giá trị của d.

A = [1, 4, 5, 7, 8, 12]    # in sorted order
Aset = set(A)

for d in range(1, 12):
    already_seen = set()
    for a in A:
        if a not in already_seen:
            b = a
            count = 1
            while b + d in Aset:
                b += d
                count += 1
                already_seen.add(b)
            print "found %d items in %d .. %d" % (count, a, b)
            # collect here the largest 'count'

Cập nhật:

  • Giải pháp này có thể đủ tốt nếu bạn chỉ quan tâm đến các giá trị của d tương đối nhỏ; ví dụ, nếu nhận được kết quả tốt nhất d <= 1000sẽ là đủ tốt. Sau đó, độ phức tạp giảm xuống O(n*1000). Điều này làm cho thuật toán gần đúng, nhưng thực sự có thể chạy được n=1000000. (Được đo ở 400-500 giây với CPython, 80-90 giây với PyPy, với một tập con số ngẫu nhiên từ 0 đến 10'000'000.)

  • Nếu bạn vẫn muốn tìm kiếm toàn bộ phạm vi và nếu trường hợp phổ biến là các chuỗi dài tồn tại, thì một cải tiến đáng chú ý là dừng lại ngay khi d quá lớn để có thể tìm thấy một chuỗi dài hơn.


Giải pháp này có thể đủ tốt nếu bạn chỉ quan tâm đến các giá trị của d tương đối nhỏ; Ví dụ: nếu nhận được kết quả tốt nhất cho d <= 1000 thì cũng đủ tốt. Sau đó, độ phức tạp giảm xuống O(n*1000), có thể chạy trong vòng chưa đầy một phút với n = 1000000 (thử cả PyPy).
Armin Rigo

Ý tưởng tốt đẹp nếu phạm vi rất hạn chế
Riad

Nhưng nếu d <= 1000bạn có thể chỉ cần loại bỏ các phần tử trùng lặp, bạn sẽ có tối đa 1000 phần tử và giải nó ở O (1000 ^ 2) sẽ hoạt động ít nhất trong vài giây Tôi brlieve.
RiaD

Không, nếu A có một triệu số từ 0 đến 10'000'000, thì m = 10'000'000. Nhưng nếu chúng ta hạn chế bản thân, d <= 1000chúng ta đang tìm kiếm các chuỗi trong toàn bộ A có nhiều nhất là 1000 như một bước.
Armin Rigo

Ah, tôi nhận được những gì bạn có nghĩa là
Riad

12

CẬP NHẬT: Tôi đã tìm thấy một bài báo về vấn đề này, bạn có thể tải xuống tại đây .

Đây là một giải pháp dựa trên lập trình động. Nó yêu cầu độ phức tạp thời gian O (n ^ 2) và độ phức tạp không gian O (n ^ 2) và không sử dụng hàm băm.

Chúng tôi giả sử tất cả các số được lưu trong mảng atheo thứ tự tăng dần và nlưu độ dài của nó. Mảng 2D l[i][j]xác định độ dài của dãy con dài nhất cách đều nhau kết thúc bằng a[i]a[j], và l[j][k]= l[i][j]+ 1 nếu a[j]- a[i]= a[k]- a[j](i <j <k).

lmax = 2
l = [[2 for i in xrange(n)] for j in xrange(n)]
for mid in xrange(n - 1):
    prev = mid - 1
    succ = mid + 1
    while (prev >= 0 and succ < n):
        if a[prev] + a[succ] < a[mid] * 2:
            succ += 1
        elif a[prev] + a[succ] > a[mid] * 2:
            prev -= 1
        else:
            l[mid][succ] = l[prev][mid] + 1
            lmax = max(lmax, l[mid][succ])
            prev -= 1
            succ += 1

print lmax

Đẹp và sạch sẽ. Nó có thể được thực hiện để chạy trong không gian tuyến tính không?
eleanora

@ user2179021 Dòng lmax = max (lmax, l [mid] [succ]) cập nhật lmax và nếu bạn muốn dãy con tối ưu, bạn cũng có thể lưu chuỗi này tại đây.
ZelluX

Bạn có nghĩ rằng nó có thể được tạo thành không gian tuyến tính?
eleanora,

@ user2179021 Tôi không thể tìm ra nó. Nhưng tôi đã tìm thấy một giải pháp khác chạy nhanh hơn trong một số trường hợp. Vui lòng xem liên kết trong câu trả lời cập nhật.
ZelluX

Cảm ơn bạn. Tuy nhiên, tôi không tin rằng phương pháp trong bài báo nhanh hơn trong thực tế. Tôi nghĩ rằng không gian thực sự là vấn đề chính (cũng như có thể sử dụng kiến ​​thức về kích thước khoảng cách có thể xảy ra để tăng tốc mọi thứ).
eleanora

3

Thuật toán

  • Vòng lặp chính duyệt qua danh sách
  • Nếu số được tìm thấy trong danh sách tính toán trước, thì nó thuộc về tất cả các chuỗi có trong danh sách đó, hãy tính lại tất cả các chuỗi với số đếm + 1
  • Xóa tất cả được tính toán trước cho phần tử hiện tại
  • Tính toán lại các chuỗi mới trong đó phần tử đầu tiên nằm trong phạm vi từ 0 đến hiện tại và phần tử thứ hai là phần tử hiện tại của truyền tải (thực tế, không phải từ 0 đến hiện tại, chúng ta có thể sử dụng thực tế là phần tử mới không được nhiều hơn giá trị max (a) và mới danh sách nên có khả năng dài hơn mà đã được tìm thấy)

Vì vậy, đối với [1, 2, 4, 5, 7]đầu ra danh sách sẽ là (nó hơi lộn xộn, hãy tự mình viết mã và xem)

  • chỉ số 0 , phần tử 1 :
    • nếu 1trong precalc? Không - không làm gì
    • Không làm gì cả
  • chỉ số 1 , phần tử 2 :
    • nếu 2trong precalc? Không - không làm gì
    • kiểm tra xem 3 = 1+ ( 2- 1) * 2 trong tập hợp của chúng ta? Không - không làm gì cả
  • chỉ số 2 , phần tử 4 :
    • nếu 4trong precalc? Không - không làm gì cả
      • kiểm tra xem 6 = 2+ ( 4- 2) * 2 trong tập hợp của chúng ta? Không
      • kiểm tra xem 7 = 1+ ( 4- 1) * 2 có trong tập hợp của chúng ta không? Có - thêm phần tử mới {7: {3: {'count': 2, 'start': 1}}} 7 - phần tử của danh sách, 3 là bước.
  • chỉ số 3 , phần tử 5:
    • nếu 5trong precalc? Không - không làm gì cả
      • không kiểm tra 46 = 4 + ( 5- 4) * 2 nhỏ hơn phần tử được tính toán đó 7
      • kiểm tra xem 8 = 2+ (5 - 2) * 2 trong tập hợp của chúng ta? Không
      • kiểm tra 10 =2 + ( 5- 1) * 2 - nhiều hơn max (a) == 7
  • chỉ số 4 , phần tử 7:
    • nếu 7 trong precalc? Có - đưa nó vào kết quả
      • không kiểm tra 59 = 5 + ( 7- 5) * 2 lớn hơn max (a) == 7

result = (3, {'count': 3, 'start': 1}) # step 3, count 3, start 1, biến nó thành chuỗi

Phức tạp

Nó không được nhiều hơn O (N ^ 2) và tôi nghĩ nó ít hơn do việc chấm dứt tìm kiếm trình tự mới sớm hơn, tôi sẽ cố gắng cung cấp phân tích chi tiết sau

def add_precalc(precalc, start, step, count, res, N):
    if step == 0: return True
    if start + step * res[1]["count"] > N: return False

    x = start + step * count
    if x > N or x < 0: return False

    if precalc[x] is None: return True

    if step not in precalc[x]:
        precalc[x][step] = {"start":start, "count":count}

    return True

def work(a):
    precalc = [None] * (max(a) + 1)
    for x in a: precalc[x] = {}
    N, m = max(a), 0
    ind = {x:i for i, x in enumerate(a)}

    res = (0, {"start":0, "count":0})
    for i, x in enumerate(a):
        for el in precalc[x].iteritems():
            el[1]["count"] += 1
            if el[1]["count"] > res[1]["count"]: res = el
            add_precalc(precalc, el[1]["start"], el[0], el[1]["count"], res, N)
            t = el[1]["start"] + el[0] * el[1]["count"]
            if t in ind and ind[t] > m:
                m = ind[t]
        precalc[x] = None

        for y in a[i - m - 1::-1]:
            if not add_precalc(precalc, y, x - y, 2, res, N): break

    return [x * res[0] + res[1]["start"] for x in range(res[1]["count"])]

1
nó tìm kiếm sự khác biệt xuất hiện hầu hết các lần, phải không? nó sẽ tìm thấy rất nhiều số 1 trong "1 2 5 6 100 101 1000 1001 1e5 1e5 + 2 1e5 + 4", trong khi câu trả lời tốt nhất là với diff = 2?
RiaD

thực sự giải pháp của tôi không phải là bộ nhớ hiệu quả, thay đổi nó anyway :)
La Mã Pekar

Bạn có thay đổi gì không? Tôi không nhận thấy sự khác biệt
RiaD

chưa, phải đi chơi thể thao, tâm trí tôi thực sự hoạt động tốt hơn khi tôi chạy, vì vậy có lẽ điều này sẽ hữu ích :) Nếu tôi tìm thấy giải pháp, tôi sẽ đặt nó ở đây. Một hiện tại của tôi là rất ngây thơ, nhờ cho nhận thấy
La Mã Pekar

3
Trong tương lai, tôi thực sự khuyên bạn không nên xóa và đăng lại câu trả lời nếu nó vô tình bị gắn nhãn là Wiki cộng đồng. Chúng tôi có thể dễ dàng đảo ngược trạng thái đó, như tôi đã làm ở đây.
Brad Larson

3

Đây là một câu trả lời khác, hoạt động đúng lúc O(n^2)và không có bất kỳ yêu cầu bộ nhớ đáng chú ý nào ngoài việc chuyển danh sách thành một tập hợp.

Ý tưởng khá ngây thơ: giống như người đăng ban đầu, nó là tham lam và chỉ kiểm tra xem bạn có thể mở rộng một dãy con từ mỗi cặp điểm đến đâu --- tuy nhiên, trước tiên hãy kiểm tra xem chúng ta đang ở đầu một dãy con chưa . Nói cách khác, từ điểm abbạn kiểm tra như thế nào đến nay bạn có thể mở rộng đến b + (b-a), b + 2*(b-a)... nhưng chỉ khia - (b-a) không phải là đã có trong các thiết lập của tất cả các điểm. Nếu đúng như vậy, thì bạn đã thấy cùng một dãy con.

Bí quyết là thuyết phục bản thân rằng sự tối ưu hóa đơn giản này đủ để giảm độ phức tạp xuống O(n^2)mức ban đầu O(n^3). Điều đó còn lại như một sự ép buộc đối với người đọc :-) Thời gian cạnh tranh với các O(n^2)giải pháp khác ở đây.

A = [1, 4, 5, 7, 8, 12]    # in sorted order
Aset = set(A)

lmax = 2
for j, b in enumerate(A):
    for i in range(j):
        a = A[i]
        step = b - a
        if b + step in Aset and a - step not in Aset:
            c = b + step
            count = 3
            while c + step in Aset:
                c += step
                count += 1
            #print "found %d items in %d .. %d" % (count, a, c)
            if count > lmax:
                lmax = count

print lmax

2

Giải pháp của bạn O(N^3)bây giờ là (bạn đã nói O(N^2) per index). Đây là O(N^2)thời gian và O(N^2)giải pháp bộ nhớ.

Ý tưởng

Nếu chúng ta biết dãy mà đi qua các chỉ số i[0], i[1], i[2], i[3]chúng ta không nên cố gắng dãy mà bắt đầu bằng i[1]i[2]hay i[2]i[3]

Lưu ý rằng tôi đã chỉnh sửa mã đó để làm cho nó dễ dàng hơn một chút bằng cách sử dụng đã được asắp xếp nhưng nó sẽ không hoạt động đối với các phần tử bằng nhau. Bạn có thể kiểm tra số lượng tối đa của các phần tử bằng nhau trongO(N) dễ dàng

Mã giả

Tôi chỉ tìm kiếm chiều dài tối đa nhưng điều đó không thay đổi bất cứ điều gì

whereInA = {}
for i in range(n):
   whereInA[a[i]] = i; // It doesn't matter which of same elements it points to

boolean usedPairs[n][n];

for i in range(n):
    for j in range(i + 1, n):
       if usedPair[i][j]:
          continue; // do not do anything. It was in one of prev sequences.

    usedPair[i][j] = true;

    //here quite stupid solution:
    diff = a[j] - a[i];
    if diff == 0:
       continue; // we can't work with that
    lastIndex = j
    currentLen = 2
    while whereInA contains index a[lastIndex] + diff :
        nextIndex = whereInA[a[lastIndex] + diff]
        usedPair[lastIndex][nextIndex] = true
        ++currentLen
        lastIndex = nextIndex

    // you may store all indicies here
    maxLen = max(maxLen, currentLen)

Suy nghĩ về việc sử dụng bộ nhớ

O(n^2)thời gian rất chậm cho 1000000 phần tử. Nhưng nếu bạn định chạy mã này trên số phần tử như vậy, vấn đề lớn nhất sẽ là việc sử dụng bộ nhớ.
Có thể làm gì để giảm bớt nó?

  • Thay đổi mảng boolean thành trường bit để lưu trữ nhiều boolean hơn trên mỗi bit.
  • Làm cho mỗi mảng boolean tiếp theo ngắn hơn vì chúng tôi chỉ sử dụng usedPairs[i][j]nếui < j

Một số kinh nghiệm học:

  • Chỉ lưu trữ các cặp chỉ dẫn đã sử dụng. (Xung đột với ý tưởng đầu tiên)
  • Remove usedPairs rằng sẽ không bao giờ sử dụng nhiều hơn (đó là dành cho như vậy i, jmà đã được chọn trong vòng lặp)

nlà một triệu, vì vậy boolean usedPairs[n][n]sẽ cần ít nhất một terabit bộ nhớ.
Michael Butscher

Ồ, không nhận thấy những ràng buộc chính xác và không nghĩ về bộ nhớ. (btw nó có thể chia cho 2, vì chúng tôi chỉ sử dụng usedPairs[i][j]cho i <j)
RiaD

@MichaelButscher, mọi cách O(n^2)cho số 1000000 đều rất chậm.
RiaD

Có, và tôi có cảm giác rằng nó không thể được thực hiện nhanh hơn (nhưng không thể chứng minh điều đó).
Michael Butscher

2
@MichaelButscher, có lẽ bằng cách nào đó chúng ta có thể sử dụng rằng nó được sắp xếp.
RiaD

1

Đây là 2 xu của tôi.

Nếu bạn có một danh sách được gọi là đầu vào:

input = [1, 4, 5, 7, 8, 12]

Bạn có thể xây dựng cấu trúc dữ liệu mà đối với mỗi điểm này (không bao gồm điểm đầu tiên), sẽ cho bạn biết điểm đó khác xa với bất kỳ ai trong số những người tiền nhiệm của nó:

[1, 4, 5, 7, 8, 12]
 x  3  4  6  7  11   # distance from point i to point 0
 x  x  1  3  4   8   # distance from point i to point 1
 x  x  x  2  3   7   # distance from point i to point 2
 x  x  x  x  1   5   # distance from point i to point 3
 x  x  x  x  x   4   # distance from point i to point 4

Bây giờ bạn đã có các cột, bạn có thể xem xét i-thmục nhập (là input[i]) và từng số ntrong cột của nó.

Những con số mà thuộc về một dãy số cách đều bao gồm input[i], là những người mà có n * jtrong i-thvị trí của cột của họ, nơi mà jlà số trận đấu đã được tìm thấy khi di chuyển các cột từ trái sang phải, cộng với k-thngười tiền nhiệm của input[i], nơi klà chỉ số của ntrong cột của input[i].

Ví dụ: nếu chúng ta xem xét i = 1, input[i] = 4, n = 3, sau đó, chúng ta có thể xác định một chuỗi comprehending 4( input[i]), 7(vì nó có một 3vị trí 1cột của nó) và 1, vì klà 0, vì vậy chúng tôi lấy tiền nhiệm đầu tiên của i.

Có thể triển khai (xin lỗi nếu mã không sử dụng ký hiệu giống như phần giải thích):

def build_columns(l):
    columns = {}
    for x in l[1:]:
        col = []
        for y in l[:l.index(x)]:
            col.append(x - y)
        columns[x] = col
    return columns

def algo(input, columns):
    seqs = []
    for index1, number in enumerate(input[1:]):
        index1 += 1 #first item was sliced
        for index2, distance in enumerate(columns[number]):
            seq = []
            seq.append(input[index2]) # k-th pred
            seq.append(number)
            matches = 1
            for successor in input[index1 + 1 :]:
                column = columns[successor]
                if column[index1] == distance * matches:
                    matches += 1
                    seq.append(successor)
            if (len(seq) > 2):
                seqs.append(seq)
    return seqs

Cái dài nhất:

print max(sequences, key=len)

Thuật toán của bạn hoạt động trong 2 giây với 300 điểm, thuật toán của tôi hoạt động 0,03 giây. Tôi đã cố gắng của bạn trên 5000 nhưng nó đã quá lâu :( làm việc của tôi 18 giây trên 5000, do đó, nó vẫn không có cách nào chúng ta có thể làm điều này cho 1000000 nhanh
La Mã Pekar

vâng, nhưng có thể chúng tôi đang thiếu một số điểm và có thể làm điều đó một cách tự do :) hoặc ít nhất là NlogN ....
Roman Pekar

0

Duyệt qua mảng, lưu giữ bản ghi về / s kết quả tối ưu và bảng có

(1) chỉ số - hiệu số phần tử trong dãy,
(2) số lượng - số phần tử trong dãy cho đến nay, và
(3) lần cuối cùng được ghi lại thành phần.

Đối với mỗi phần tử mảng, hãy xem sự khác biệt so với mỗi phần tử mảng trước đó; nếu phần tử đó nằm cuối cùng trong một chuỗi được lập chỉ mục trong bảng, hãy điều chỉnh chuỗi đó trong bảng và cập nhật chuỗi tốt nhất nếu có, nếu không, hãy bắt đầu một chuỗi mới, trừ khi giá trị tối đa hiện tại lớn hơn độ dài của chuỗi có thể.

Quét ngược, chúng ta có thể dừng quá trình quét của mình khi d lớn hơn giữa phạm vi của mảng; hoặc khi giá trị tối đa hiện tại lớn hơn độ dài của chuỗi có thể, với d lớn hơn sự khác biệt được lập chỉ mục lớn nhất. Trình tự ở đâus[j] lớn hơn phần tử cuối cùng trong dãy sẽ bị xóa.

Tôi đã chuyển đổi mã của mình từ JavaScript sang Python (mã python đầu tiên của tôi):

import random
import timeit
import sys

#s = [1,4,5,7,8,12]
#s = [2, 6, 7, 10, 13, 14, 17, 18, 21, 22, 23, 25, 28, 32, 39, 40, 41, 44, 45, 46, 49, 50, 51, 52, 53, 63, 66, 67, 68, 69, 71, 72, 74, 75, 76, 79, 80, 82, 86, 95, 97, 101, 110, 111, 112, 114, 115, 120, 124, 125, 129, 131, 132, 136, 137, 138, 139, 140, 144, 145, 147, 151, 153, 157, 159, 161, 163, 165, 169, 172, 173, 175, 178, 179, 182, 185, 186, 188, 195]
#s = [0, 6, 7, 10, 11, 12, 16, 18, 19]

m = [random.randint(1,40000) for r in xrange(20000)]
s = list(set(m))
s.sort()

lenS = len(s)
halfRange = (s[lenS-1] - s[0]) // 2

while s[lenS-1] - s[lenS-2] > halfRange:
    s.pop()
    lenS -= 1
    halfRange = (s[lenS-1] - s[0]) // 2

while s[1] - s[0] > halfRange:
    s.pop(0)
    lenS -=1
    halfRange = (s[lenS-1] - s[0]) // 2

n = lenS

largest = (s[n-1] - s[0]) // 2
#largest = 1000 #set the maximum size of d searched

maxS = s[n-1]
maxD = 0
maxSeq = 0
hCount = [None]*(largest + 1)
hLast = [None]*(largest + 1)
best = {}

start = timeit.default_timer()

for i in range(1,n):

    sys.stdout.write(repr(i)+"\r")

    for j in range(i-1,-1,-1):
        d = s[i] - s[j]
        numLeft = n - i
        if d != 0:
            maxPossible = (maxS - s[i]) // d + 2
        else:
            maxPossible = numLeft + 2
        ok = numLeft + 2 > maxSeq and maxPossible > maxSeq

        if d > largest or (d > maxD and not ok):
            break

        if hLast[d] != None:
            found = False
            for k in range (len(hLast[d])-1,-1,-1):
                tmpLast = hLast[d][k]
                if tmpLast == j:
                    found = True
                    hLast[d][k] = i
                    hCount[d][k] += 1
                    tmpCount = hCount[d][k]
                    if tmpCount > maxSeq:
                        maxSeq = tmpCount
                        best = {'len': tmpCount, 'd': d, 'last': i}
                elif s[tmpLast] < s[j]:
                    del hLast[d][k]
                    del hCount[d][k]
            if not found and ok:
                hLast[d].append(i)
                hCount[d].append(2)
        elif ok:
            if d > maxD: 
                maxD = d
            hLast[d] = [i]
            hCount[d] = [2]


end = timeit.default_timer()
seconds = (end - start)

#print (hCount)
#print (hLast)
print(best)
print(seconds)

vì vậy bạn vẫn cần O (N ^ 2) bộ nhớ
La Mã Pekar

@RomanPekar Cảm ơn bạn đã nhận xét. Điều gì về tối ưu hóa được thêm vào để kết thúc quá trình quét ngược sớm?
גלעד ברקן

@RomanPeckar không, chỉ nghĩ về thuật toán thôi; Tôi cũng không biết Python.
גלעד ברקן

@RomanPeckar đã thêm ví dụ jsfiddle vào câu trả lời của tôi. jsfiddle.net/groovy/b6zkR
גלעד ברקן

Tôi nghĩ có nhiều giải pháp O (N ^ 2), tôi đã viết một ví dụ như thế này hôm nay. Tôi vẫn không biết nếu có giải pháp tốt mà có thể chạy cho 1000000 số ...
La Mã Pekar

0

Đây là một trường hợp cụ thể cho vấn đề chung chung hơn được mô tả ở đây: Khám phá các mẫu dài trong đó K = 1 và được cố định. Ở đó, nó có thể được giải trong O (N ^ 2). Runnig thực hiện thuật toán C của tôi được đề xuất ở đó, mất 3 giây để tìm lời giải cho N = 20000 và M = 28000 trong máy 32 bit của tôi.


0

Phương pháp tham lam
1. Chỉ một chuỗi quyết định được tạo ra.
2. Nhiều quyết định được tạo ra. Lập trình động 1. Nó không đảm bảo luôn luôn đưa ra một giải pháp tối ưu.
2. Nó chắc chắn đưa ra một giải pháp tối ưu.

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.