Thuật toán: cách hiệu quả để loại bỏ các số nguyên trùng lặp khỏi một mảng


92

Tôi gặp sự cố này từ một cuộc phỏng vấn với Microsoft.

Cho một mảng các số nguyên ngẫu nhiên, hãy viết một thuật toán trong C để loại bỏ các số trùng lặp và trả về các số duy nhất trong mảng ban đầu.

Vd: Đầu vào: {4, 8, 4, 1, 1, 2, 9} Đầu ra:{4, 8, 1, 2, 9, ?, ?}

Một lưu ý là thuật toán mong đợi không nên yêu cầu mảng phải được sắp xếp trước. Và khi một phần tử đã bị loại bỏ, các phần tử sau cũng phải được dịch chuyển về phía trước. Dù sao, giá trị của các phần tử ở phần cuối của mảng nơi các phần tử được dịch chuyển về phía trước là không đáng kể.

Cập nhật: Kết quả phải được trả về trong mảng ban đầu và không nên sử dụng cấu trúc dữ liệu trợ giúp (ví dụ: bảng băm). Tuy nhiên, tôi đoán việc bảo quản đơn hàng là không cần thiết.

Cập nhật2: Đối với những người thắc mắc tại sao lại có những ràng buộc không thực tế này, đây là một câu hỏi phỏng vấn và tất cả những ràng buộc này sẽ được thảo luận trong quá trình suy nghĩ để xem tôi có thể đưa ra những ý tưởng khác nhau như thế nào.


4
Bạn có phải bảo toàn thứ tự của các số duy nhất không?
Douglas Leeder

1
Kết quả có phải được trả về trong mảng ban đầu không?
Douglas Leeder

1
Tôi đã cập nhật câu hỏi. Kết quả sẽ được trả về trong mảng ban đầu. Tuy nhiên, thứ tự của dãy không quan trọng.
ejel

3
Thật là khó chịu khi ai đó đặt câu trả lời của họ vào câu hỏi và các câu trả lời khác. Chỉ cần kiên nhẫn, mọi người sẽ đạt được điều đó.
GManNickG

2
Tại sao bảng băm không được phép? Hạn chế đó không có ý nghĩa gì.
RBarryYoung

Câu trả lời:


19

Làm thế nào về:

void rmdup(int *array, int length)
{
    int *current , *end = array + length - 1;

    for ( current = array + 1; array < end; array++, current = array + 1 )
    {
        while ( current <= end )
        {
            if ( *current == *array )
            {
                *current = *end--;
            }
            else
            {
                current++;
            }
        }
    }
}

Phải là O (n ^ 2) trở xuống.


3
Đây là giải pháp đơn giản và có nhiều khả năng là những gì mà câu hỏi phỏng vấn đang tìm kiếm.
Kirk Broadhurst

7
Họ thậm chí có thể đang kiểm tra để thấy rằng bạn không bị say mê với việc tối ưu hóa quá sớm trừ khi họ cũng đưa ra các ràng buộc về thời gian chạy cho bạn! :-)
Trevor Tippins

16
Lol, mặc dù việc sắp xếp mảng và làm việc trên mảng đã được sắp xếp nhanh hơn hẳn. Việc sắp xếp phải được cung cấp bởi một API và không được tối ưu hóa quá sớm.
ziggystar

2
Không nên là while (hiện tại <= end) thay vì while (hiện tại <kết thúc)?
Shail

2
Tại sao điều này được chấp nhận là câu trả lời đúng? Nếu bảo toàn thứ tự là không cần thiết thì tốt hơn là chỉ sử dụng sắp xếp hợp nhất O (nlogn) và sau đó loại bỏ các phần tử lặp lại trong O (n) ... tổng độ phức tạp - O (nlogn) tốt hơn nhiều so với giải pháp này.
Pawan

136

Một giải pháp được bạn gái tôi gợi ý là một biến thể của sắp xếp hợp nhất. Sửa đổi duy nhất là trong bước hợp nhất, chỉ cần bỏ qua các giá trị trùng lặp. Giải pháp này cũng sẽ là O (n log n). Trong cách tiếp cận này, việc sắp xếp / loại bỏ trùng lặp được kết hợp với nhau. Tuy nhiên, tôi không chắc liệu điều đó có tạo ra sự khác biệt nào không.


8
Đề xuất tuyệt vời, nhưng bạn sẽ cần một số sổ sách kế toán để theo dõi cuối mỗi đầu ra hợp nhất. Tôi thực sự đã làm điều này một lần và có, việc loại bỏ các bản sao khi bạn hợp nhất sẽ làm cho nó nhanh hơn nhiều.
Mark Ransom

2
Không rõ liệu O (N / 2) khoảng trống thừa có được tính là "cấu trúc dữ liệu trợ giúp" bị cấm trong câu hỏi hay không - Tôi không biết liệu hạn chế nhằm quy định O (1) không gian thừa hay chỉ để quy định rằng câu trả lời không nên phụ thuộc vào việc triển khai cấu trúc dữ liệu lớn. Có thể một hợp nhất tiêu chuẩn là tốt. Nhưng nếu không, mẹo hàng đầu: đừng cố gắng viết một loại hợp nhất tại chỗ trong một cuộc phỏng vấn, trừ khi bạn thực sự biết mình đang làm gì.
Steve Jessop

Ý tưởng tuyệt vời. Nhưng nó yêu cầu rằng dữ liệu còn lại phải giữ trật tự ban đầu.
Hardy Feng

4
Một bài báo mô tả những gì bạn gái của bạn đề xuất như sau: dc-pubs.dbs.uni-leipzig.de/files/…
Mike B

50

Tôi đã đăng bài này một lần trước đây trên SO, nhưng tôi sẽ sao chép nó ở đây vì nó khá hay. Nó sử dụng băm, xây dựng một cái gì đó giống như một bộ băm tại chỗ. Nó được đảm bảo là O (1) trong không gian nách (đệ quy là một lệnh gọi đuôi), và thường là độ phức tạp thời gian O (N). Thuật toán như sau:

  1. Lấy phần tử đầu tiên của mảng, đây sẽ là trạm gác.
  2. Sắp xếp lại phần còn lại của mảng, càng nhiều càng tốt, sao cho mỗi phần tử ở vị trí tương ứng với hàm băm của nó. Khi bước này hoàn thành, các bản sao sẽ được phát hiện. Đặt chúng bằng sentinel.
  3. Di chuyển tất cả các phần tử có chỉ số bằng băm đến đầu mảng.
  4. Di chuyển tất cả các phần tử bằng sentinel, ngoại trừ phần tử đầu tiên của mảng, đến cuối mảng.
  5. Những gì còn lại giữa các phần tử được băm đúng cách và các phần tử trùng lặp sẽ là những phần tử không thể được đặt trong chỉ mục tương ứng với hàm băm của chúng do xung đột. Định kỳ để đối phó với các yếu tố này.

Điều này có thể được chứng minh là O (N) với điều kiện không có tình huống bệnh lý nào trong phép băm: Ngay cả khi không có bản sao, khoảng 2/3 phần tử sẽ bị loại bỏ ở mỗi lần đệ quy. Mỗi mức đệ quy là O (n) trong đó n nhỏ là lượng phần tử còn lại. Vấn đề duy nhất là, trong thực tế, nó chậm hơn so với sắp xếp nhanh khi có ít bản sao, tức là có nhiều va chạm. Tuy nhiên, khi có một lượng lớn các bản sao, nó nhanh đến kinh ngạc.

Chỉnh sửa: Trong các triển khai hiện tại của D, hash_t là 32 bit. Mọi thứ về thuật toán này giả định rằng sẽ có rất ít, nếu có, xung đột băm trong không gian 32 bit đầy đủ. Tuy nhiên, va chạm có thể xảy ra thường xuyên trong không gian mô đun. Tuy nhiên, giả định này rất có thể sẽ đúng với bất kỳ tập dữ liệu nào có kích thước hợp lý. Nếu khóa nhỏ hơn hoặc bằng 32 bit, nó có thể là hàm băm của chính nó, nghĩa là không thể xảy ra xung đột trong không gian 32 bit đầy đủ. Nếu nó lớn hơn, đơn giản là bạn không thể lắp đủ chúng vào không gian địa chỉ bộ nhớ 32-bit thì đó là một vấn đề. Tôi giả sử hash_t sẽ được tăng lên 64 bit trong các triển khai 64 bit của D, trong đó tập dữ liệu có thể lớn hơn. Hơn nữa, nếu điều này từng được chứng minh là một vấn đề, người ta có thể thay đổi hàm băm ở mỗi mức đệ quy.

Đây là cách triển khai bằng ngôn ngữ lập trình D:

void uniqueInPlace(T)(ref T[] dataIn) {
    uniqueInPlaceImpl(dataIn, 0);
}

void uniqueInPlaceImpl(T)(ref T[] dataIn, size_t start) {
    if(dataIn.length - start < 2)
        return;

    invariant T sentinel = dataIn[start];
    T[] data = dataIn[start + 1..$];

    static hash_t getHash(T elem) {
        static if(is(T == uint) || is(T == int)) {
            return cast(hash_t) elem;
        } else static if(__traits(compiles, elem.toHash)) {
            return elem.toHash;
        } else {
            static auto ti = typeid(typeof(elem));
            return ti.getHash(&elem);
        }
    }

    for(size_t index = 0; index < data.length;) {
        if(data[index] == sentinel) {
            index++;
            continue;
        }

        auto hash = getHash(data[index]) % data.length;
        if(index == hash) {
            index++;
            continue;
        }

        if(data[index] == data[hash]) {
            data[index] = sentinel;
            index++;
            continue;
        }

        if(data[hash] == sentinel) {
            swap(data[hash], data[index]);
            index++;
            continue;
        }

        auto hashHash = getHash(data[hash]) % data.length;
        if(hashHash != hash) {
            swap(data[index], data[hash]);
            if(hash < index)
                index++;
        } else {
            index++;
        }
    }


    size_t swapPos = 0;
    foreach(i; 0..data.length) {
        if(data[i] != sentinel && i == getHash(data[i]) % data.length) {
            swap(data[i], data[swapPos++]);
        }
    }

    size_t sentinelPos = data.length;
    for(size_t i = swapPos; i < sentinelPos;) {
        if(data[i] == sentinel) {
            swap(data[i], data[--sentinelPos]);
        } else {
            i++;
        }
    }

    dataIn = dataIn[0..sentinelPos + start + 1];
    uniqueInPlaceImpl(dataIn, start + swapPos + 1);
}

1
Câu trả lời cực kỳ hay, được đánh giá thấp! Tôi thích ý tưởng sử dụng phần tử ở vị trí 1 làm giá trị sentinel. Nếu tôi có thể đưa ra một vài đề xuất nhỏ, tôi sẽ thay đổi bước 2 để bao gồm "mỗi phần tử ở vị trí tương ứng với mô-đun băm của nó đối với kích thước mảng " và có thể làm rõ rằng các bản sao được đặt thành sentinel là các phần tử có cùng giá trị (trái ngược với cùng một hàm băm hoặc cùng kích thước mảng mô-đun băm).
j_random_hacker

20

Một cách triển khai hiệu quả hơn

int i, j;

/* new length of modified array */
int NewLength = 1;

for(i=1; i< Length; i++){

   for(j=0; j< NewLength ; j++)
   {

      if(array[i] == array[j])
      break;
   }

   /* if none of the values in index[0..j] of array is not same as array[i],
      then copy the current value to corresponding new position in array */

  if (j==NewLength )
      array[NewLength++] = array[i];
}

Trong cách triển khai này, không cần sắp xếp mảng. Ngoài ra, nếu một phần tử trùng lặp được tìm thấy, không cần chuyển tất cả các phần tử sau phần tử này theo một vị trí.

Đầu ra của mã này là mảng [] với kích thước NewLength

Ở đây chúng ta sẽ bắt đầu từ elemt thứ 2 trong mảng và so sánh nó với tất cả các phần tử trong mảng cho đến mảng này. Chúng tôi đang giữ một biến chỉ mục bổ sung 'NewLength' để sửa đổi mảng đầu vào. Biến thể NewLength được khởi tạo thành 0.

Phần tử trong mảng [1] sẽ được so sánh với mảng [0]. Nếu chúng khác nhau, thì giá trị trong mảng [NewLength] sẽ được sửa đổi với mảng [1] và tăng NewLength. Nếu chúng giống nhau, NewLength sẽ không được sửa đổi.

Vì vậy, nếu chúng ta có một mảng [1 2 1 3 1], thì

Trong lần truyền đầu tiên của vòng lặp 'j', mảng [1] (2) sẽ được so sánh với mảng0, sau đó 2 sẽ được ghi vào mảng [NewLength] = array [1] vì vậy mảng sẽ là [1 2] vì NewLength = 2

Trong lần truyền thứ hai của vòng lặp 'j', mảng [2] (1) sẽ được so sánh với mảng0 và mảng1. Ở đây vì mảng [2] (1) và mảng0 là cùng một vòng lặp sẽ bị phá vỡ ở đây. vì vậy mảng sẽ là [1 2] vì NewLength = 2

và như thế


3
Tốt lắm. Tôi có một đề xuất để cải thiện. Vòng lặp lồng nhau thứ hai có thể được thay đổi để cho (j = 0; j <NewLength; j ++) và cuối cùng nếu kiểm tra có thể được thay đổi để if (j == NewLength)
Vadakkumpadath

Đó là một gợi ý tuyệt vời. Tôi đã cập nhật mã dựa trên ur comment
Byju

Không thành công ít nhất nếu chúng ta có các giá trị giống nhau trong mảng {1,1,1,1,1,1}. Mã vô dụng.
Yuriy Chernyshov

Sự phức tạp của cái này là gì, không phải nó cũng là O (n ^ 2) sao?
JavaSa

1
Rất nhiều phiếu tán thành, nhưng điều này không hiệu quả: nó là O (n ^ 2) khi có ít bản sao.
Paul Hankin

19

Nếu bạn đang tìm kiếm ký hiệu O cao cấp, thì sắp xếp mảng với kiểu sắp xếp O (n log n) thì thực hiện duyệt O (n) có thể là cách tốt nhất. Nếu không sắp xếp, bạn đang nhìn vào O (n ^ 2).

Chỉnh sửa: nếu bạn chỉ làm số nguyên, thì bạn cũng có thể thực hiện sắp xếp cơ số để lấy O (n).


Câu trả lời của Jeff B chỉ là O (n). Bộ băm và từ điển băm là đầu gối của những con ong.
ChrisW

3
ChrisW: bộ băm / từ điển chỉ là O (1) nếu bạn giả sử không có va chạm. (Tôi không nói rằng tôi sẽ không sử dụng chúng cho vấn đề này - tôi có lẽ sẽ - nó chỉ là một sai lầm để khẳng định rằng họ đang thực sự O (1).)
Laurence Gonsalves

2
Trên thực tế, vì bạn đã biết trước kích thước của mảng, bạn có thể đảm bảo O (1). Sau đó, bạn có thể đánh đổi các va chạm với lượng bộ nhớ bổ sung mà bạn sử dụng.
Vitali

Bạn có thể muốn suy nghĩ lại rằng phản đối - các điều kiện mới được đăng cho vấn đề làm cho giải pháp của Jeff B không hợp lệ.
Mark Ransom

3
Bạn có thể muốn giải thích kỹ hơn về "traversal", vì một phương pháp xóa ngây thơ có thể dẫn đến O (n ^ 2) cho số lượng lớn các bản sao.
Mark Ransom

11

1. Sử dụng thêm O (1) không gian, trong O (n log n) thời gian

Điều này có thể, ví dụ:

  • đầu tiên thực hiện sắp xếp O (n log n) tại chỗ
  • sau đó xem qua danh sách một lần, viết bản sao đầu tiên của mọi trở lại đầu danh sách

Tôi tin rằng đối tác của ejel đúng rằng cách tốt nhất để làm điều này sẽ là sắp xếp hợp nhất tại chỗ với bước hợp nhất đơn giản hóa và đó có thể là mục đích của câu hỏi, nếu bạn là người chẳng hạn. viết một hàm thư viện mới để thực hiện điều này một cách hiệu quả nhất có thể mà không có khả năng cải thiện các đầu vào và sẽ có những trường hợp hữu ích nếu làm như vậy mà không có bảng băm, tùy thuộc vào các loại đầu vào. Nhưng tôi chưa thực sự kiểm tra điều này.

2. Sử dụng thêm O (nhiều) không gian, trong O (n) thời gian

  • khai báo một mảng zero'd đủ lớn để chứa tất cả các số nguyên
  • đi qua mảng một lần
  • đặt phần tử mảng tương ứng thành 1 cho mỗi số nguyên.
  • Nếu nó đã là 1, hãy bỏ qua số nguyên đó.

Điều này chỉ hoạt động nếu có một số giả định đáng ngờ:

  • nó có thể làm mất bộ nhớ một cách rẻ tiền hoặc kích thước của các int nhỏ so với số lượng của chúng
  • bạn rất vui khi yêu cầu hệ điều hành của mình cung cấp bộ nhớ 256 ^ sizepof (int)
  • và nó sẽ lưu vào bộ nhớ cache cho bạn một cách thực sự hiệu quả nếu nó khổng lồ

Đó là một câu trả lời tồi, nhưng nếu bạn có RẤT NHIỀU phần tử đầu vào, nhưng chúng đều là số nguyên 8 bit (hoặc thậm chí có thể là số nguyên 16 bit) thì đó có thể là cách tốt nhất.

3. O (little) -ish extra space, O (n) -ish time

Như # 2, nhưng sử dụng bảng băm.

4. Con đường rõ ràng

Nếu số lượng phần tử nhỏ, việc viết một thuật toán thích hợp sẽ không hữu ích nếu mã khác viết nhanh hơn và đọc nhanh hơn.

Ví dụ. Đi qua mảng cho từng phần tử duy nhất (ví dụ: phần tử đầu tiên, phần tử thứ hai (bản sao của phần tử đầu tiên đã bị loại bỏ), v.v.) loại bỏ tất cả các phần tử giống nhau. O (1) không gian phụ, O (n ^ 2) thời gian.

Ví dụ. Sử dụng các hàm thư viện để thực hiện việc này. hiệu quả phụ thuộc mà bạn có dễ dàng có sẵn.


7

Vâng, nó thực hiện cơ bản là khá đơn giản. Đi qua tất cả các phần tử, kiểm tra xem có trùng lặp trong các phần tử còn lại hay không và chuyển phần còn lại lên chúng.

Nó không hiệu quả khủng khiếp và bạn có thể tăng tốc nó bằng một mảng trợ giúp cho đầu ra hoặc cây phân loại / nhị phân, nhưng điều này dường như không được phép.


1
OTOH, mã bổ sung cần thiết để triển khai cây sắp xếp có thể kém hiệu quả (bộ nhớ) hơn so với giải pháp đơn giản và có lẽ kém hiệu quả hơn trong thời gian chạy đối với các mảng nhỏ (ví dụ dưới 100 phần tử).
TMN

6

Nếu bạn được phép sử dụng C ++, một lệnh gọi std::sortsau đó là lệnh gọi đến std::uniquesẽ cho bạn câu trả lời. Độ phức tạp về thời gian là O (N log N) đối với sắp xếp và O (N) đối với truyền tải duy nhất.

Và nếu C ++ thì khỏi bàn, sẽ không có thứ gì ngăn các thuật toán tương tự này được viết bằng C.


"Một lưu ý là thuật toán dự kiến ​​không nên yêu cầu mảng phải được sắp xếp trước."
sbi

2
Nó không nói rằng bạn không thể sắp xếp mảng một khi bạn nhận được nó ... Không sử dụng O (N) sắp xếp bộ nhớ ngoài là cách duy nhất để thực hiện điều đó trong O (N log N) hoặc tốt hơn.
Greg Rogers

Đối với mục đích của vấn đề, không nên sử dụng các utils thư viện chuẩn. Về việc phân loại, tuy nhiên, tôi càng nghĩ về nó, tôi càng không chắc liệu nó có ổn hay không.
ejel

1
Tôi nghĩ rằng các câu trả lời liên quan đến các hàm tiêu chuẩn C ++ và C ++ rất hữu ích, ngay cả khi chúng không trả lời câu hỏi ban đầu, vì chúng cung cấp câu trả lời tròn trịa hơn cho những người tìm thấy câu hỏi này sau này.
Douglas Leeder

6

Bạn có thể làm điều này trong một lần duyệt, nếu bạn sẵn sàng hy sinh trí nhớ. Bạn có thể chỉ cần kiểm đếm xem bạn đã thấy một số nguyên hay chưa trong một mảng băm / kết hợp. Nếu bạn đã nhìn thấy một số, hãy xóa nó khi bạn tiếp tục hoặc tốt hơn, di chuyển các số bạn chưa nhìn thấy vào một mảng mới, tránh bất kỳ sự thay đổi nào trong mảng ban đầu.

Trong Perl:

foreach $i (@myary) {
    if(!defined $seen{$i}) {
        $seen{$i} = 1;
        push @newary, $i;
    }
}

Không rõ câu trả lời có phải nằm trong mảng ban đầu hay không.
Douglas Leeder

Để làm điều này mà không yêu cầu một mảng mới, bạn có thể chỉ cần thay thế bản sao bằng một phần tử xuất hiện ở cuối mảng và thực hiện lại vòng lặp hiện tại, vì vấn đề không xác định thứ tự quan trọng. Điều này đòi hỏi phải kiểm tra thêm một số giới hạn, nhưng rất khả thi.
Jeff B

6
Đây là một ý tưởng hay, cho đến khi câu hỏi được chỉnh sửa. Ý tưởng bảng băm của bạn rõ ràng là trái với các quy tắc.
WCWedin

14
Tôi không hiểu tại sao câu trả lời này được bình chọn nhiều nhất. Nó được viết bằng perl và sử dụng các tính năng quan trọng không có sẵn trong C, như câu hỏi đặt ra.
LiraNuna

5
câu hỏi yêu cầu cho mã c, không phải perl. sử dụng perl giúp bạn có hashtables và "push" miễn phí. Nếu tôi có thể làm điều đó trong scala bạn sẽ chỉ cần gọi input.removeDuplicates, nhưng tôi nghi ngờ rằng có thể đã được chấp nhận để người phỏng vấn :)
Peter Recore

5

Giá trị trả về của hàm phải là số phần tử duy nhất và chúng đều được lưu trữ ở phía trước của mảng. Nếu không có thông tin bổ sung này, bạn thậm chí sẽ không biết liệu có bất kỳ bản sao nào không.

Mỗi lần lặp của vòng lặp ngoài xử lý một phần tử của mảng. Nếu nó là duy nhất, nó nằm ở phía trước của mảng và nếu là một bản sao, nó sẽ bị ghi đè bởi phần tử chưa được xử lý cuối cùng trong mảng. Giải pháp này chạy trong thời gian O (n ^ 2).

#include <stdio.h>
#include <stdlib.h>

size_t rmdup(int *arr, size_t len)
{
  size_t prev = 0;
  size_t curr = 1;
  size_t last = len - 1;
  while (curr <= last) {
    for (prev = 0; prev < curr && arr[curr] != arr[prev]; ++prev);
    if (prev == curr) {
      ++curr;
    } else {
      arr[curr] = arr[last];
      --last;
    }
  }
  return curr;
}

void print_array(int *arr, size_t len)
{
  printf("{");
  size_t curr = 0;
  for (curr = 0; curr < len; ++curr) {
    if (curr > 0) printf(", ");
    printf("%d", arr[curr]);
  }
  printf("}");
}

int main()
{
  int arr[] = {4, 8, 4, 1, 1, 2, 9};
  printf("Before: ");
  size_t len = sizeof (arr) / sizeof (arr[0]);
  print_array(arr, len);
  len = rmdup(arr, len);
  printf("\nAfter: ");
  print_array(arr, len);
  printf("\n");
  return 0;
}

4

Đây là một phiên bản Java.

int[] removeDuplicate(int[] input){

        int arrayLen = input.length;
        for(int i=0;i<arrayLen;i++){
            for(int j = i+1; j< arrayLen ; j++){
                if(((input[i]^input[j]) == 0)){
                    input[j] = 0;
                }
                if((input[j]==0) && j<arrayLen-1){
                        input[j] = input[j+1];
                        input[j+1] = 0;
                    }               
            }
        }       
        return input;       
    }

Không thành công ít nhất với các đầu vào tiếp theo: {1,1,1,1,1,1,1} {0,0,0.0,0,1,1,1,1,1,1}
Yuriy Chernyshov

3

Đây là giải pháp của tôi.

///// find duplicates in an array and remove them

void unique(int* input, int n)
{
     merge_sort(input, 0, n) ;

     int prev = 0  ;

     for(int i = 1 ; i < n ; i++)
     {
          if(input[i] != input[prev])
               if(prev < i-1)
                   input[prev++] = input[i] ;                         
     }
}

2

Một mảng rõ ràng phải được "chuyển ngang" từ phải sang trái để tránh sao chép liên tục các giá trị qua lại.

Nếu bạn có bộ nhớ không giới hạn, bạn có thể phân bổ một mảng bit cho các sizeof(type-of-element-in-array) / 8byte để mỗi bit biểu thị cho dù bạn đã gặp giá trị tương ứng hay chưa.

Nếu bạn không, tôi không thể nghĩ ra điều gì tốt hơn là lướt qua một mảng và so sánh từng giá trị với các giá trị theo sau nó và sau đó nếu tìm thấy trùng lặp, hãy xóa hoàn toàn các giá trị này. Đây là nơi nào đó gần O (n ^ 2) (hoặc O ((n ^ 2-n) / 2) ).

IBM có một bài báo về chủ đề gần giống.


Thật vậy - một lần vượt qua O (n) để tìm phần tử lớn nhất sẽ không làm tăng tổng chi phí O ().
Douglas Leeder

2

Hãy xem nào:

  • O (N) vượt qua để tìm phân bổ tối thiểu / tối đa
  • mảng bit để tìm thấy
  • O (N) chuyển hoán đổi các bản sao để kết thúc.

Cho rằng chúng chỉ là số nguyên, để đơn giản, bạn có thể giả sử 32 bit và không cần bận tâm tìm kiếm min / max: 2 ^ 32 bit là "chỉ" 512MB, vì vậy việc tìm kiếm giới hạn chỉ là sử dụng bộ nhớ và tối ưu hóa thời gian O (1) (được cấp, một tối ưu hóa khổng lồ trong trường hợp của ví dụ đã cho). Và nếu chúng là 64 bit, điều đó không liên quan vì bạn không biết rằng tối thiểu và tối đa sẽ không cách xa nhau hơn số bit bộ nhớ bạn có.
Steve Jessop

Lý thuyết sang một bên, sẽ không phân bổ 512MB mất nhiều thời gian hơn là tìm tối thiểu / tối đa?
LiraNuna

Phụ thuộc vào lượng dữ liệu có và giá trị tối thiểu / tối đa là bao nhiêu. Nếu bạn đang xem dữ liệu đầu vào hơn 512MB, thì có thể nhanh hơn để tránh vượt qua O (N) thừa đó. Tất nhiên nếu bạn đang xem nhiều dữ liệu đầu vào như vậy, thì ít có khả năng bạn có 512MB để dự phòng. Trong trường hợp giá trị tối thiểu / tối đa gần bằng 0 / INT_MAX, thì việc tối ưu hóa cũng không giúp được gì. Tôi chỉ nói rằng mặc dù bước đầu tiên rõ ràng là hữu ích cho các số nhỏ, nó không thể tránh khỏi thực tế là thuật toán này sử dụng các bit UINT_MAX trong trường hợp xấu nhất, vì vậy bạn cần phải lập kế hoạch cho hạn chế đó.
Steve Jessop

Bạn cũng có thể đúng - trong mọi trường hợp, việc làm rõ câu hỏi có nghĩa là việc sử dụng mảng bit không còn nữa. Tôi sẽ để lại câu trả lời này trong trường hợp ai đó đến sau mà không bị ràng buộc và muốn xem tất cả các câu trả lời có thể.
Douglas Leeder

2

Điều này có thể được thực hiện trong một lần với thuật toán O (N log N) và không cần thêm bộ nhớ.

Tiến hành từ phần tử a[1]đến a[N]. Ở mỗi giai đoạn i, tất cả các phần tử ở bên trái a[i]bao gồm một đống phần tử được sắp xếp a[0]thông qua a[j]. Trong khi đó, chỉ mục thứ hai j, ban đầu là 0, theo dõi kích thước của đống.

Kiểm tra a[i]và chèn nó vào đống, mà bây giờ chiếm yếu tố a[0]để a[j+1]. Khi phần tử được chèn vào, nếu gặp một phần tử trùng lặp a[k]có cùng giá trị, không chèn a[i]vào heap (tức là loại bỏ nó); nếu không thì chèn nó vào đống, mà bây giờ phát triển bởi một yếu tố và bây giờ bao gồm a[0]tới a[j+1], và increment j.

Tiếp tục theo cách này, tăng dần icho đến khi tất cả các phần tử của mảng đã được kiểm tra và chèn vào heap, cuối cùng sẽ chiếm a[0]đến a[j]. jlà chỉ số của phần tử cuối cùng của heap và heap chỉ chứa các giá trị phần tử duy nhất.

int algorithm(int[] a, int n)
{
    int   i, j;  

    for (j = 0, i = 1;  i < n;  i++)
    {
        // Insert a[i] into the heap a[0...j]
        if (heapInsert(a, j, a[i]))
            j++;
    }
    return j;
}  

bool heapInsert(a[], int n, int val)
{
    // Insert val into heap a[0...n]
    ...code omitted for brevity...
    if (duplicate element a[k] == val)
        return false;
    a[k] = val;
    return true;
}

Nhìn vào ví dụ, đây không phải là chính xác những gì được yêu cầu vì mảng kết quả bảo toàn thứ tự phần tử ban đầu. Nhưng nếu yêu cầu này được nới lỏng, thuật toán ở trên sẽ thực hiện thủ thuật.


1

Trong Java, tôi sẽ giải quyết nó như thế này. Không biết viết cái này bằng C.

   int length = array.length;
   for (int i = 0; i < length; i++) 
   {
      for (int j = i + 1; j < length; j++) 
      {
         if (array[i] == array[j]) 
         {
            int k, j;
            for (k = j + 1, l = j; k < length; k++, l++) 
            {
               if (array[k] != array[i]) 
               {
                  array[l] = array[k];
               }
               else
               {
                  l--;
               }
            }
            length = l;
         }
      }
   }

Nếu bạn ghi đè các bản sao bạn tìm thấy bằng giá trị ở cuối mảng, bạn có thể tránh được sự dịch chuyển của toàn bộ mảng trong vòng lặp for () bên trong của bạn. Điều đó sẽ đưa bạn đến O (n ^ 2) từ O (n ^ 3). Thực hiện C của tôi là nổi xung quanh đây đâu đó ...
mocj

Tôi nghĩ, thay đổi là một phần của yêu cầu, nhưng bạn đúng tất nhiên.
Dominik

1
@mocj: Tôi thích giải pháp của bạn, trông rất thanh lịch. Nhưng tôi nghĩ rằng nó không hoạt động nếu hai phần tử cuối cùng bằng nhau, bởi vì bạn ngừng kiểm tra sự bằng nhau trước phần tử cuối cùng. (comenting ở đây vì có quá xem danh tiếng bình luận bất cứ nơi nào khác :()
Dominik

Bạn đúng ngoại trừ vấn đề ban đầu nói rằng các giá trị ở cuối mảng là không đáng kể. Vì bạn không trả về độ dài của mảng đã sửa đổi nên sự khác biệt giữa giá trị cuối cùng và giá trị thứ hai đến cuối cùng là không quan trọng khi hai giá trị bằng nhau. Nơi nào người gọi giải thích sự kết thúc của mảng trở lại được
mocj

1

Làm thế nào về sau đây?

int* temp = malloc(sizeof(int)*len);
int count = 0;
int x =0;
int y =0;
for(x=0;x<len;x++)
{
    for(y=0;y<count;y++)
    {
        if(*(temp+y)==*(array+x))
        {
            break;
        }
    }
    if(y==count)
    {
        *(temp+count) = *(array+x);
        count++;
    }
}
memcpy(array, temp, sizeof(int)*len);

Tôi cố gắng khai báo một mảng tạm thời và đặt các phần tử vào đó trước khi sao chép mọi thứ trở lại mảng ban đầu.


1

Sau khi xem xét vấn đề, đây là cách delphi của tôi, có thể hữu ích

var
A: Array of Integer;
I,J,C,K, P: Integer;
begin
C:=10;
SetLength(A,10);
A[0]:=1; A[1]:=4; A[2]:=2; A[3]:=6; A[4]:=3; A[5]:=4;
A[6]:=3; A[7]:=4; A[8]:=2; A[9]:=5;

for I := 0 to C-1 do
begin
  for J := I+1 to C-1 do
    if A[I]=A[J] then
    begin
      for K := C-1 Downto J do
        if A[J]<>A[k] then
        begin
          P:=A[K];
          A[K]:=0;
          A[J]:=P;
          C:=K;
          break;
        end
        else
        begin
          A[K]:=0;
          C:=K;
        end;
    end;
end;

//tructate array
setlength(A,C);
end;

1

Ví dụ sau sẽ giải quyết vấn đề của bạn:

def check_dump(x):
   if not x in t:
      t.append(x)
      return True

t=[]

output = filter(check_dump, input)

print(output)
True

1
import java.util.ArrayList;


public class C {

    public static void main(String[] args) {

        int arr[] = {2,5,5,5,9,11,11,23,34,34,34,45,45};

        ArrayList<Integer> arr1 = new ArrayList<Integer>();

        for(int i=0;i<arr.length-1;i++){

            if(arr[i] == arr[i+1]){
                arr[i] = 99999;
            }
        }

        for(int i=0;i<arr.length;i++){
            if(arr[i] != 99999){

                arr1.add(arr[i]);
            }
        }

        System.out.println(arr1);
}
    }

arr [i + 1] có nên ném ArrayIndexOutOfBoundsException cho phần tử cuối cùng không?
Thứ Bảy,

@Sathesh Không. Bởi vì "<arr.length-1"
GabrielBB

1

Đây là giải pháp ngây thơ (N * (N-1) / 2). Nó sử dụng không gian bổ sung liên tục và duy trì thứ tự ban đầu. Nó tương tự như giải pháp của @Byju, nhưng không sử dụng if(){}khối. Nó cũng tránh sao chép một phần tử vào chính nó.

#include <stdio.h>
#include <stdlib.h>

int numbers[] = {4, 8, 4, 1, 1, 2, 9};
#define COUNT (sizeof numbers / sizeof numbers[0])

size_t undup_it(int array[], size_t len)
{
size_t src,dst;

  /* an array of size=1 cannot contain duplicate values */
if (len <2) return len; 
  /* an array of size>1 will cannot at least one unique value */
for (src=dst=1; src < len; src++) {
        size_t cur;
        for (cur=0; cur < dst; cur++ ) {
                if (array[cur] == array[src]) break;
                }
        if (cur != dst) continue; /* found a duplicate */

                /* array[src] must be new: add it to the list of non-duplicates */
        if (dst < src) array[dst] = array[src]; /* avoid copy-to-self */
        dst++;
        }
return dst; /* number of valid alements in new array */
}

void print_it(int array[], size_t len)
{
size_t idx;

for (idx=0; idx < len; idx++)  {
        printf("%c %d", (idx) ? ',' :'{' , array[idx] );
        }
printf("}\n" );
}

int main(void) {    
    size_t cnt = COUNT;

    printf("Before undup:" );    
    print_it(numbers, cnt);    

    cnt = undup_it(numbers,cnt);

    printf("After undup:" );    
    print_it(numbers, cnt);

    return 0;
}

0

Điều này có thể được thực hiện trong một lần chuyển, trong thời gian O (N) với số lượng nguyên trong danh sách đầu vào và O (N) lưu trữ với số lượng nguyên duy nhất.

Đi qua danh sách từ trước ra sau, với hai con trỏ "dst" và "src" được khởi tạo cho mục đầu tiên. Bắt đầu với một bảng băm trống "số nguyên được nhìn thấy". Nếu số nguyên tại src không có trong hàm băm, hãy ghi nó vào vị trí tại vị trí dst và tăng dần dst. Thêm số nguyên tại src vào băm, sau đó tăng src. Lặp lại cho đến khi src vượt qua cuối danh sách đầu vào.


2
Trong sửa đổi cho câu hỏi ban đầu, bảng băm không được phép. Tuy nhiên, phương pháp tiếp cận hai con trỏ của bạn là một cách hay để thu gọn đầu ra khi bạn đã xác định được các bản sao.
Mark Ransom

0

Chèn tất cả các phần tử trong dấu binary tree the disregards duplicates- O(nlog(n)). Sau đó, trích xuất tất cả chúng trở lại trong mảng bằng cách thực hiện chuyển ngang - O(n). Tôi giả định rằng bạn không cần bảo quản đơn đặt hàng.


0

Sử dụng bộ lọc nở để băm. Điều này sẽ làm giảm chi phí bộ nhớ rất đáng kể.


quan tâm để xây dựng hoặc cung cấp một tài liệu tham khảo?
dldnh

0

Trong JAVA,

    Integer[] arrayInteger = {1,2,3,4,3,2,4,6,7,8,9,9,10};

    String value ="";

    for(Integer i:arrayInteger)
    {
        if(!value.contains(Integer.toString(i))){
            value +=Integer.toString(i)+",";
        }

    }

    String[] arraySplitToString = value.split(",");
    Integer[] arrayIntResult = new Integer[arraySplitToString.length];
    for(int i = 0 ; i < arraySplitToString.length ; i++){
        arrayIntResult[i] = Integer.parseInt(arraySplitToString[i]);
    }

đầu ra: {1, 2, 3, 4, 6, 7, 8, 9, 10}

hy vọng điều này sẽ giúp


1
Kiểm tra điều này với đầu vàoarrayInteger = {100,10,1};
Blastfurnace


0

Đầu tiên, bạn nên tạo một mảng check[n]trong đó n là số phần tử của mảng mà bạn muốn tạo không trùng lặp và đặt giá trị của mọi phần tử (của mảng kiểm tra) bằng 1. Sử dụng vòng lặp for duyệt qua mảng với trùng lặp, cho biết tên của nó là arr, và trong vòng lặp lặp lại viết thế này:

{
    if (check[arr[i]] != 1) {
        arr[i] = 0;
    }
    else {
        check[arr[i]] = 0;
    }
}

Với điều đó, bạn đặt mọi bản sao bằng 0. Vì vậy, điều duy nhất còn lại phải làm là duyệt qua arrmảng và in mọi thứ mà nó không bằng 0. Thứ tự vẫn còn và nó mất thời gian tuyến tính (3 * n).


Câu hỏi không cho phép sử dụng cấu trúc dữ liệu phụ.
ejel

0

Cho một mảng gồm n phần tử, hãy viết thuật toán để loại bỏ tất cả các phần tử trùng lặp khỏi mảng trong thời gian O (nlogn)

Algorithm delete_duplicates (a[1....n])
//Remove duplicates from the given array 
//input parameters :a[1:n], an array of n elements.

{

temp[1:n]; //an array of n elements. 

temp[i]=a[i];for i=1 to n

 temp[i].value=a[i]

temp[i].key=i

 //based on 'value' sort the array temp.

//based on 'value' delete duplicate elements from temp.

//based on 'key' sort the array temp.//construct an array p using temp.

 p[i]=temp[i]value

  return p.

Trong các phần tử khác được duy trì trong mảng đầu ra bằng cách sử dụng 'khóa'. Coi khóa có độ dài là O (n), thời gian thực hiện sắp xếp khóa và giá trị là O (nlogn). Vì vậy, thời gian cần thiết để xóa tất cả các bản sao khỏi mảng là O (nlogn).


Đối với tất cả các glyphs đậm, bạn đã làm helper data structure (e.g. hashtable) should not be usedgì?
greybeard

Không nhất thiết phải cần. Tôi chỉ đánh dấu những điều đó với mục đích hiểu rõ.
Sharief Muzammil

0

đây là những gì tôi đã có, mặc dù nó đặt sai thứ tự mà chúng ta có thể sắp xếp tăng dần hoặc giảm dần để sửa chữa nó.

#include <stdio.h>
int main(void){
int x,n,myvar=0;
printf("Enter a number: \t");
scanf("%d",&n);
int arr[n],changedarr[n];

for(x=0;x<n;x++){
    printf("Enter a number for array[%d]: ",x);
    scanf("%d",&arr[x]);
}
printf("\nOriginal Number in an array\n");
for(x=0;x<n;x++){
    printf("%d\t",arr[x]);
}

int i=0,j=0;
// printf("i\tj\tarr\tchanged\n");

for (int i = 0; i < n; i++)
{
    // printf("%d\t%d\t%d\t%d\n",i,j,arr[i],changedarr[i] );
    for (int j = 0; j <n; j++)
    {   
        if (i==j)
        {
            continue;

        }
        else if(arr[i]==arr[j]){
            changedarr[j]=0;

        }
        else{
            changedarr[i]=arr[i];

        }
    // printf("%d\t%d\t%d\t%d\n",i,j,arr[i],changedarr[i] );
    }
    myvar+=1;
}
// printf("\n\nmyvar=%d\n",myvar);
int count=0;
printf("\nThe unique items:\n");
for (int i = 0; i < myvar; i++)
{
        if(changedarr[i]!=0){
            count+=1;
            printf("%d\t",changedarr[i]);   
        }
}
    printf("\n");
}

-1

Sẽ thật tuyệt nếu bạn có một DataStructure tốt có thể nhanh chóng cho biết liệu nó có chứa một số nguyên hay không. Có lẽ là một loại cây nào đó.

DataStructure elementsSeen = new DataStructure();
int elementsRemoved = 0;
for(int i=0;i<array.Length;i++){
  if(elementsSeen.Contains(array[i])
    elementsRemoved++;
  else
    array[i-elementsRemoved] = array[i];
}
array.Length = array.Length - elementsRemoved;
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.