Làm thế nào để kiểm tra xem hai chuỗi có hoán vị lẫn nhau bằng cách sử dụng không gian bổ sung O (1) không?


13

Cho hai chuỗi làm thế nào bạn có thể kiểm tra xem chúng có phải là hoán vị của nhau bằng cách sử dụng không gian O (1) không? Sửa đổi các chuỗi không được phép theo bất kỳ cách nào.
Lưu ý: Không gian O (1) liên quan đến cả độ dài chuỗi VÀ kích thước của bảng chữ cái.


3
Bạn nghĩ sao? Bạn đã thử những gì, và bạn đã bị mắc kẹt ở đâu? Là các chuỗi trên một bảng chữ cái kích thước không đổi? Bạn đã thử tính toán biểu đồ của họ?
Yuval Filmus

@YuvalFilmus nó phải là không gian O (1) cả về chiều dài của chuỗi và kích thước của bảng chữ cái
Ẩn danh

Điều này dường như rõ ràng là không thể. Bất kỳ thuật toán nào cũng sẽ cần thêm không gian để lưu trữ ít nhất một vị trí trong một chuỗi hoặc một ký tự. Cả hai điều này đều không phải là O (1).
David Schwartz

@DavidSchwartz - thế nào? O (1) có nghĩa là hằng số, không phải một bute. Không quan trọng chuỗi dài bao nhiêu, vị trí trong đó là một số.
Thưởng thức

Nó phụ thuộc vào mô hình máy, rõ ràng không có vấn đề trong các mô hình đồng phục. Trong mô hình chi phí logarit lưu trữ chỉ mục O(log n)dành cho các chuỗi có độ dài n không phải là hằng số bằng chiều dài cũng như kích thước bảng chữ cái. Khi các chuỗi có thể được sửa đổi tạm thời, tôi nghĩ có một giải pháp với bảng chữ cái tăng lên là tuyến tính theo kích thước bảng chữ cái nhưng không đổi về độ dài chuỗi trong mô hình logarit.
kap

Câu trả lời:


7

Cách tiếp cận ngây thơ sẽ là xây dựng biểu đồ của cả hai chuỗi và kiểm tra xem chúng có giống nhau không. Vì chúng tôi không được phép lưu trữ cấu trúc dữ liệu như vậy (có kích thước sẽ tuyến tính với kích thước của bảng chữ cái) có thể được tính trong một lần, chúng tôi cần tính các lần xuất hiện của từng biểu tượng có thể sau biểu tượng khác:

function count(letter, string)
    var count := 0
    foreach element in string
        if letter = element
            count++
    return count

function samePermutation(stringA, stringB)
    foreach s in alphabet
        if count(s, stringA) != count(s, stringB)
            return false
    return true

Tất nhiên, điều này giả định rằng các chỉ số đếm và lặp là các số nguyên có kích thước không đổi, thay vì phụ thuộc vào độ dài của chuỗi.


Để tối ưu hóa, bạn có thể đi qua một mảng và chỉ tính toán biểu đồ của các chữ cái bạn gặp phải. Theo cách này, sự phức tạp trở nên độc lập với kích thước bảng chữ cái.
Yuval Filmus

Để mở rộng trên nhận xét @YuvalFilmus, bạn cũng cần 1) kiểm tra xem độ dài chuỗi có giống nhau hay 2) lặp trên cả hai chuỗi đầu vào. Bạn cần một trong số này vì có thể một số chữ cái này không phải là một chữ cái khác. Tùy chọn 1 nên có ít tính toán hơn.
BurnsBA

@YuvalFilmus Tôi muốn tránh điều đó vì nó có nghĩa là độ phức tạp thời gian bậc hai, tôi hy vọng bảng chữ cái sẽ nhỏ hơn kích thước chuỗi trung bình. Đối với các chuỗi nhỏ và bảng chữ cái có thứ tự, tôi sẽ xem xét tính toán biểu tượng hiện tại nhỏ nhất tiếp theo cùng với số đếm trong vòng lặp bên trong, để người ta có thể bỏ qua một vài lần lặp của vòng lặp bảng chữ cái - với độ phức tạp O(n * min(n, |Σ|)). Hừm, bây giờ tôi nghĩ về nó, nghe có vẻ giống như giải pháp "được phép lặp lại" từ câu trả lời của bạn, phải không?
Bergi

countkhông O(1)(nghĩa là nó có thể tràn)
Revierpost

1
@Eternalcode Tôi chưa bao giờ nói đó countint:-) Vâng, nó sẽ không hoạt động, nhưng trong Java thì điều đó không thể xảy ra bằng mọi cách
Bergi

12

Suy ra các mảng bằng và giả sử chúng có độ dài n .A,Bn

Giả sử đầu tiên rằng các giá trị trong mỗi mảng là khác biệt. Đây là một thuật toán sử dụng không gian :O(1)

  1. Tính toán các giá trị tối thiểu của cả hai mảng và kiểm tra xem chúng có giống nhau không.

  2. Tính toán các giá trị tối thiểu thứ hai của cả hai mảng và kiểm tra xem chúng có giống nhau không.

  3. Và như thế.

Tính toán giá trị tối thiểu của một mảng rõ ràng sử dụng không gian . Với phần tử nhỏ nhất thứ k , chúng ta có thể tìm thấy phần tử nhỏ nhất ( k + 1 ) bằng cách tìm giá trị tối thiểu lớn hơn phần tử nhỏ thứ k (ở đây chúng ta sử dụng thực tế là tất cả các phần tử đều khác biệt).O(1)k(k+1)k

Khi các yếu tố được phép lặp lại, chúng tôi sửa đổi thuật toán như sau:

  1. Tính các giá trị tối thiểu của cả hai mảng, đếm số lần mỗi lần xuất hiện và xác minh m A , 1 = m B , 1 và các số đếm giống hệt nhau.mA,1,mB,1mA,1=mB,1

  2. Tính các giá trị tối thiểu lớn hơn m A , 1 , m B , 1 trong hai mảng (tương ứng) và đếm số lần mỗi lần xuất hiện. Xác nhận rằng m A , 2 = m B , 2 và các số đếm giống hệt nhau.mA,2,mB,2mA,1,mB,1mA,2=mB,2

  3. Và như thế.


1
Cách tiếp cận này có phải là vì dường như là cách duy nhất để tìm phần tử min trong không gian O ( 1 ) và truy cập chỉ đọc vào mảng là lặp lại trên tất cả các phần tử? O(n2)O(1)
ryan

4
Điều này đòi hỏi một thứ tự trên bảng chữ cái, mặc dù thật dễ dàng để thay đổi thuật toán không yêu cầu điều đó. Tuy nhiên, trong trường hợp "có trùng lặp", điều này đòi hỏi không gian , không phải O ( 1 ) . Đếm mất không gian. O(lgn)O(1)
Derek Elkins rời SE

7
Đếm không cần không gian (logarit), nhưng - theo định nghĩa này về sử dụng không gian - thậm chí còn lặp lại trên mảng. Do đó, dưới ý nghĩa nghiêm ngặt của việc sử dụng không gian, không có cách nào để làm điều đó trong không gian liên tục.
Daniel Jour

4
@DanielJour, nó phụ thuộc vào mô hình chi phí bạn đang sử dụng. Theo chi phí thống nhất, điều này là có thể trong không gian liên tục.
ryan

7
Nếu bạn chỉ được phép có số bit không đổi, bạn chỉ có thể xử lý các bảng chữ cái có kích thước không đổi (điều này tuân theo lý thuyết của các ngôn ngữ thông thường).
Yuval Filmus

2

Xác định một số hàm f (c) ánh xạ một số ký tự c thành một số nguyên tố duy nhất (a = 2, b = 3, c = 5, v.v.).

set checksum = 1
set count = 0 <-- this is probably not even necessary, but it's another level of check
for character c in string 1
    checksum = checksum * f(c)
    count = count + 1
for character c in string 2
    checksum = checksum / f(c)
    count = count = 1

permutation = count == 0 and checksum == 1

Chỉ cần tuyên bố rằng bạn có thể sử dụng chức năng ánh xạ số nguyên tố là một chút thuận tay và rất có thể xảy ra sự cố giữ không gian .O(1)


Với một ràng buộc trên bảng chữ cái, nên sử dụng không gian O ( 1 ) , nếu không tôi tin rằng nó sẽ không phải là không gian không đổi. Hơn nữa, nếu bạn đã tính toán nó trong không gian O ( 1 ), nó sẽ cực kỳ kém hiệu quả dựa trên kết quả hiện tại . Tuy nhiên, +1 cho cách tiếp cận nguyên thủy. f(c)O(1)O(1)
ryan

Một vấn đề khác tôi nhận ra sau khi đăng bài là tổng kiểm tra sẽ là một con số khổng lồ cho các chuỗi lớn, đến mức mà chính nó có thể vi phạm yêu cầu không gian O (1). Điều này có thể được giải quyết bằng cách sử dụng số float và xen kẽ bởi một ký tự trên một chuỗi rồi chia cho chuỗi kia, sau đó chỉ cần nói tổng kiểm tra phải gần 1. Các chuỗi sẽ phải thực sự khổng lồ vì lỗi dấu phẩy động là một vấn đề.
Alex Stasse

4
Những câu trả lời như vậy là lý do chúng ta cần cẩn thận với mô hình tính toán của mình. Mô hình thông thường chúng ta sử dụng khi phân tích thuật toán đếm bộ nhớ theo đơn vị từ máy , có kích thước bit . Vì vậy, bạn không thể thực hiện tính toán trong các số nguyên. Nếu bạn chuyển sang dấu phẩy động, thuật toán của bạn có thể thất bại ngay cả khi hai chuỗi là hoán vị của nhau và ngược lại, không nhất thiết phải đưa ra câu trả lời đúng khi không. O(logn)
Yuval Filmus

4
Điều này không sử dụng không gian liên tục. Ngay cả đối với một bảng chữ cái cố định, kích thước của checksum số nguyên sẽ là bit cho đầu vào có độ dài n . Θ(n)n
David Richerby

0

Bạn có thể làm điều này là O(nlogn). Sắp xếp hai chuỗi và so sánh chúng theo chỉ mục. Nếu chúng khác nhau ở bất cứ đâu, chúng không phải là hoán vị của nhau.

Đối với một O(n)giải pháp, băm có thể được sử dụng. Hàm băm này sẽ hoạt động và eđối với bất kỳ chữ cái nào cũng sẽ là giá trị ascii của nó. Nếu hai giá trị băm của các chuỗi khác nhau, chúng không phải là hoán vị của nhau.

Hàm băm trong liên kết:

Một ứng cử viên tiềm năng có thể là điều này. Sửa một số nguyên lẻ R. Với mỗi phần tử e bạn muốn băm tính hệ số (R + 2 * e). Sau đó tính sản phẩm của tất cả các yếu tố này. Cuối cùng chia sản phẩm cho 2 để lấy băm.

Yếu tố 2 trong (R + 2e) đảm bảo rằng tất cả các yếu tố là số lẻ, do đó tránh việc sản phẩm sẽ trở thành 0. Việc chia 2 ở cuối là vì sản phẩm sẽ luôn là số lẻ, do đó phép chia chỉ loại bỏ một bit không đổi .

Ví dụ: tôi chọn R = 1779033703. Đây là một lựa chọn tùy ý, thực hiện một số thử nghiệm sẽ hiển thị nếu một R cho trước là tốt hay xấu. Giả sử giá trị của bạn là [1, 10, 3, 18]. Sản phẩm (được tính bằng int 32 bit) là

(R + 2) * (R + 20) * (R + 6) * (R + 36) = 3376724311 Do đó, hàm băm sẽ là

3376724311/2 = 1688362155.

Sử dụng băm kép (hoặc cho mức độ quá mức thậm chí nhiều hơn) bằng cách thay đổi giá trị của R sẽ xác định thành công chúng là hoán vị với xác suất rất cao .


1
Bạn không thể sắp xếp các chuỗi vì bạn không được phép sửa đổi chúng. Đối với băm, nó là một thuật toán ngẫu nhiên có thể đưa ra câu trả lời sai.
Yuval Filmus

0

Giả sử bạn có hai chuỗi được gọi là s và t.

Bạn có thể sử dụng phương pháp phỏng đoán để đảm bảo rằng chúng không bằng nhau.

  1. s.length == t.length
  2. tổng số ký tự của s == tổng số ký tự trong t
  3. [giống như trong 2. nhưng với xor thay vì sum]

Sau này, bạn có thể dễ dàng chạy một thuật toán để chứng minh rằng chuỗi bằng nhau.

  1. sắp xếp một chuỗi bằng với chuỗi kia và so sánh (O (n ^ 2))
  2. sắp xếp cả hai và so sánh (O (2n log (n))
  3. kiểm tra từng char trong s nếu có cùng số lượng trong cả hai chuỗi (O (n ^ 2))

Tất nhiên bạn không thể sắp xếp nhanh như vậy nếu bạn không được phép sử dụng thêm không gian. Vì vậy, việc bạn chọn thuật toán nào không quan trọng - mỗi thuật toán sẽ cần chạy trong thời gian O (n ^ 2) khi chỉ có không gian O (1) và nếu heuristic không thể chứng minh rằng chúng không thể bằng nhau.


3
" Sửa đổi các chuỗi không được phép theo bất kỳ cách nào. "
Bergi

0

Trong mã kiểu C cho toàn bộ thói quen:

for (int i = 0; i < n; i++) {
   int k = -1;
   next: for (int j = 0; j <= i; j++)
       if (A[j] == A[i]) {
          while (++k < n)
              if (B[k] == A[i])
                  continue next;
          return false; // note at this point j == i
       }
}
return true; 

Hoặc trong mã giả rất dài (sử dụng lập chỉ mục dựa trên 1)

// our loop invariant is that B contains a permutation of the letters
// in A[1]..A[i-1]
for i=1..n
   if !checkLetters(A, B, i)
      return false
return true

trong đó hàm checkLetters (A, B, i) kiểm tra xem nếu có M bản sao của A [i] trong A [1] .. A [i], thì có ít nhất M bản sao của A [i] trong B:

checkLetters(A,B,i)
    k = 0 // scan index into B
    for j=1..i
      if A[j] = A[i]
         k = findNextValue(B, k+1, A[i])
         if k > n
            return false
    return true

và hàm findNextValue tìm kiếm trong B để tìm giá trị bắt đầu từ một chỉ mục và trả về chỉ mục nơi nó được tìm thấy (hoặc n + 1 nếu không tìm thấy).

n2


Bạn có thể vui lòng chuyển đổi mã C của bạn thành mã giả? Đây không phải là một trang web lập trình.
Yuval Filmus

Đây có vẻ như là một biến thể khác của câu trả lời của Bergi (với một số khác biệt không quan trọng).
Yuval Filmus

O(nm)O(n2)

0

O(n3n

Lặp lại string1string2, cho mỗi ký tự kiểm tra tần suất có thể được tìm thấy trong string1string2. Tôi là một nhân vật thường ở trong một chuỗi hơn trong một chuỗi khác, nó không phải là một hoán vị. Nếu tần số của tất cả các ký tự bằng nhau thì các chuỗi là hoán vị của nhau.

Đây là một miếng trăn để làm điều này chính xác

s1="abcaba"
s2="aadbba"

def check_if_permutations(string1, string2):
  for string in [string1, string2]:
    # string references string1 
    #  string2, it is not a copy
    for char in string:
      count1=0
      for char1 in string1:
        if  char==char1:
          count1+=1
      count2=0
      for char2 in string2:
        if  char==char2:
          count2+=1
      if count1!=count2:
        print('unbalanced character',char)
        return()
  print ("permutations")
  return()

check_if_permutations(s1,s2)

stringstring1string2charchar1char2O(logn)count1count2string[string1, string2]

Tất nhiên bạn thậm chí không cần các biến đếm nhưng có thể sử dụng các con trỏ.

s1="abcaba"
s2="aadbba"

def check_if_permutations(string1, string2):
  for string in [string1, string2]:
    # string references one of string1 
    # or string2, it is not a copy
    for char in string:
      # p1 and p2 should be views as pointers
      p1=0
      p2=0
      while (p1<len(string1)) and (p2<len(string2)):
        # p1>=len(string1): p1 points to beyond end of string
        while (p1<len(string1)) and (string1[p1]!=char) :
          p1+=1
        while(p2<len(string2)) and (string2[p2]!=char):
          p2+=1
        if (p1<len(string1)) != (p2<len(string2)):
          print('unbalanced character',char)
          return()
        p1+=1
        p2+=1
  print ("permutations")
  return()

check_if_permutations(s1,s2)

O(log(n))

n


Điều này giống như giải pháp của Bergi dưới đây.
Yuval Filmus

@YuvalFilmus Không, nó không lặp lại trên toàn bộ bảng chữ cái và do đó thời gian chạy của nó không phụ thuộc vào kích thước bảng chữ cái. Nó chỉ sử dụng hai chuỗi nên được kiểm tra. Ngoài ra chương trình thứ hai tránh đếm.
phép lạ173

@YuvalFilmus Tôi thấy bây giờ, rằng các nhận xét của bạn và các điểm khác theo hướng tôi đã sử dụng trong chương trình của mình.
phép lạ173
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.