O (n) समय और O (1) स्थान में डुप्लिकेट ढूँढना


121

इनपुट: ऐसे n तत्वों की एक सरणी को देखते हुए जिनमें 0 से n-1 तक के तत्व होते हैं, इनमें से किसी भी संख्या में कोई भी संख्या दिखाई देती है।

लक्ष्य: O (n) में इन दोहराए गए नंबरों को खोजने और केवल निरंतर मेमोरी स्पेस का उपयोग करने के लिए।

उदाहरण के लिए, n को 7 और सरणी को {1, 2, 3, 1, 3, 0, 6} होना चाहिए, उत्तर 1 और 3 होना चाहिए। मैंने यहां भी इसी तरह के प्रश्नों की जांच की, लेकिन उत्तर में कुछ डेटा संरचनाओं का उपयोग किया गया जैसे HashSetआदि।

उसी के लिए कोई कुशल एल्गोरिदम?

जवाबों:


164

यह वह चीज है जिसके साथ मुझे अतिरिक्त साइन बिट की आवश्यकता नहीं है:

for i := 0 to n - 1
    while A[A[i]] != A[i] 
        swap(A[i], A[A[i]])
    end while
end for

for i := 0 to n - 1
    if A[i] != i then 
        print A[i]
    end if
end for

पहला लूप सरणी की अनुमति देता है ताकि यदि तत्व xकम से कम एक बार मौजूद हो, तो उन प्रविष्टियों में से एक स्थिति में हो A[x]

ध्यान दें कि यह पहले ब्लश में ओ (एन) नहीं लग सकता है, लेकिन यह है - हालांकि इसमें नेस्टेड लूप है, यह अभी भी O(N)समय में चलता है । एक स्वैप केवल तब होता है जब कोई ऐसा होता iहै A[i] != i, और प्रत्येक स्वैप कम से कम एक तत्व सेट करता है A[i] == i, जहां यह पहले सच नहीं था। इसका मतलब है कि स्वैप की कुल संख्या (और इस प्रकार whileलूप बॉडी के निष्पादन की कुल संख्या ) सबसे अधिक है N-1

दूसरा लूप ऐसे मानों को प्रिंट करता है xजिनके लिए A[x]बराबर नहीं है x- चूंकि पहला लूप गारंटी देता है कि यदि xसरणी में कम से कम एक बार मौजूद है, तो उनमें से एक उदाहरण होगा A[x], इसका मतलब है कि यह उन मानों को प्रिंट करता है xजिनमें मौजूद नहीं हैं सरणी।

(Ideone लिंक ताकि आप इसके साथ खेल सकें)


10
@arasmussen: हाँ। मैं पहले एक टूटे हुए संस्करण के साथ आया था, हालाँकि। समस्या के विरोधाभास समाधान के लिए एक संकेत देते हैं - तथ्य यह है कि प्रत्येक वैध सरणी मूल्य पर भी एक वैध सरणी सूचकांक संकेत है a[a[i]], और swap()ऑपरेशन में ओ (1) अंतरिक्ष बाधा संकेत प्रमुख है।
कैफ़े

2
@caf: कृपया अपना कोड सरणी के साथ {3,4,5,3,4} के रूप में चलाएं यह विफल रहता है।
निर्मलगृहे

6
@ निर्मलजी: यह एक वैध इनपुट 5नहीं है , क्योंकि सीमा में नहीं है 0..N-1( Nइस मामले में 5)।
कैफे

2
@caf {1,2,3,1,3,0,0,0,0,6} के लिए आउटपुट 3 1 0 0 0 है या किसी भी मामले में जहां पुनरावृत्ति 2 से अधिक है। क्या यह सही ओ / पी है?
टर्मिनल

3
ये अद्भुत है! मैंने इस प्रश्न पर कई प्रकार के संस्करण देखे हैं, आमतौर पर अधिक विवश है, और इसे हल करने का सबसे सामान्य तरीका है जो मैंने देखा है। मैं बस इस printबात का उल्लेख करूंगा कि स्टेटमेंट को print iबदलकर इसे stackoverflow.com/questions/5249985/… के समाधान में बदल दिया गया है और ("बैग" एक परिवर्तनीय सारणी है) stackoverflow.com/questions/3492302- "का QK ।
j_random_hacker

35

कैफ़े का शानदार उत्तर प्रत्येक संख्या को प्रिंट करता है जो कि सरणी के -१ में k बार दिखाई देता है। यह उपयोगी व्यवहार है, लेकिन सवाल यकीनन प्रत्येक डुप्लिकेट को केवल एक बार मुद्रित करने के लिए कहता है, और वह रैखिक समय / निरंतर अंतरिक्ष सीमा को उड़ाने के बिना ऐसा करने की संभावना के लिए संकेत देता है। यह निम्नलिखित छद्मकोश के साथ अपने दूसरे लूप को बदलकर किया जा सकता है:

for (i = 0; i < N; ++i) {
    if (A[i] != i && A[A[i]] == A[i]) {
        print A[i];
        A[A[i]] = i;
    }
}

यह उस संपत्ति का शोषण करता है जो पहले लूप के चलने के बाद, यदि कोई मूल्य mएक से अधिक बार दिखाई देता है, तो उन दिखावे में से एक को सही स्थिति में होने की गारंटी दी जाती है, अर्थात् A[m]। यदि हम सावधान रहें तो हम उस "घर" स्थान का उपयोग कर सकते हैं ताकि किसी डुप्लिकेट को अभी तक मुद्रित किया गया है या नहीं, इस बारे में जानकारी संग्रहीत करने के लिए।

कैफे के संस्करण में, जैसा कि हम सरणी के माध्यम से गए थे, A[i] != iनिहित है कि A[i]एक डुप्लिकेट है। मेरे संस्करण में, मैं थोड़ा अलग अपरिवर्तनीय पर भरोसा करता हूं: A[i] != i && A[A[i]] == A[i]इसका मतलब है कि A[i]एक डुप्लिकेट है जो हमने पहले नहीं देखा है । (यदि आप "वह जिसे हमने पहले नहीं देखा है" भाग में छोड़ देते हैं, तो बाकी को कैफ़े के अपरिवर्तनीय की सच्चाई से निहित होने के लिए देखा जा सकता है, और गारंटी है कि सभी डुप्लिकेट के पास एक घर के स्थान में कुछ कॉपी है।) शुरू (कैफे के 1 लूप खत्म होने के बाद) और मैं नीचे दिखाता हूं कि यह प्रत्येक चरण के बाद बनाए रखा जाता है।

जैसा कि हम सरणी से गुजरते हैं, A[i] != iपरीक्षण के हिस्से पर सफलता का मतलब है कि एक डुप्लिकेट A[i] हो सकता है जो पहले नहीं देखा गया है। यदि हमने इसे पहले नहीं देखा है, तो हम A[i]अपने आप को इंगित करने के लिए घर के स्थान की अपेक्षा करते हैं - यही ifस्थिति की दूसरी छमाही तक परीक्षण किया जाता है । अगर ऐसा है, तो हम इसे प्रिंट करते हैं और घर का स्थान बदलकर इस पहले पाए गए डुप्लिकेट को वापस इंगित करते हैं, जिससे 2-चरण "चक्र" बनता है।

यह देखने के लिए कि यह ऑपरेशन हमारे आक्रमणकारी को परिवर्तित नहीं करता है, मान लीजिए कि वह m = A[i]किसी विशेष स्थिति के लिए iसंतोषजनक है A[i] != i && A[A[i]] == A[i]। यह स्पष्ट है कि हम जो परिवर्तन करते हैं ( A[A[i]] = i) अन्य गैर-घरेलू घटनाओं को mआउटपुट के रूप में डुप्लिकेट होने से रोकने के लिए काम करेंगे, जिससे उनकी ifस्थिति का 2 आधा हिस्सा विफल हो जाएगा, लेकिन क्या यह iघर के स्थान पर आने पर काम करेगा m? हां यह होगा, क्योंकि अब, भले ही इस नए पर iहम पाते हैं कि ifहालत का 1 आधा हिस्सा A[i] != i, सच है, 2 आधा परीक्षण है कि क्या यह जिस स्थान को इंगित करता है वह एक घर का स्थान है और पाता है कि यह नहीं है। इस स्थिति में हम नहीं जानते कि डुप्लिकेट मान था mया नहीं A[m], लेकिन हम जानते हैं कि दोनों ही तरह से,यह पहले ही रिपोर्ट किया जा चुका है , क्योंकि इन 2-चक्रों को कैफे के 1 लूप के परिणाम में प्रदर्शित नहीं होने की गारंटी है। (ध्यान दें कि यदि m != A[m]तब वास्तव में से एक mऔर A[m]एक बार से अधिक होता है, और अन्य सभी पर नहीं होती है।)


1
हां, यह बहुत कुछ वैसा ही है, जैसा मैं लेकर आया हूं। यह दिलचस्प है कि एक समान पहला लूप कई अलग-अलग समस्याओं के लिए उपयोगी है, बस एक अलग प्रिंटिंग लूप के साथ।
कैफ़े

22

यहाँ छद्मकोश है

for i <- 0 to n-1:
   if (A[abs(A[i])]) >= 0 :
       (A[abs(A[i])]) = -(A[abs(A[i])])
   else
      print i
end for

C ++ में नमूना कोड


3
बहुत चालाक - अनुक्रमित प्रविष्टि के साइन बिट में जवाब एन्कोडिंग!
Holtavolt

3
@ शशांग: यह नहीं हो सकता। समस्या विनिर्देशन देखें। "एन तत्वों की एक सरणी को देखते हुए जिसमें 0 से एन -1 तक के तत्व होते हैं "
प्रसून सौरव

5
यह डुप्लिकेट 0s का पता नहीं लगाएगा, और एक ही नंबर को कई बार डुप्लिकेट होने के रूप में स्पॉट करेगा।
शून्य सेट अप

1
@ नल सेट: आप केवल शून्य मुद्दे के -साथ बदल सकते हैं ~
user541686

26
यह जवाब हो सकता है कि समस्या चल रही है, लेकिन तकनीकी रूप से यह O(n)छिपे हुए स्थान का उपयोग करता है - nसाइन बिट्स। यदि सरणी को इस तरह परिभाषित किया गया है कि प्रत्येक तत्व केवल 0और के बीच मान रख सकता है n-1, तो यह स्पष्ट रूप से काम नहीं करता है।
कैफे

2

अपेक्षाकृत छोटे N के लिए हम div / mod के संचालन का उपयोग कर सकते हैं

n.times do |i|
  e = a[i]%n
  a[e] += n
end

n.times do |i| 
  count = a[i]/n
  puts i if count > 1
end

सी / सी ++ नहीं बल्कि वैसे भी

http://ideone.com/GRZPI


+1 अच्छा समाधान। दो बार के बाद एक प्रविष्टि में n रोकना बड़ा n समायोजित करेगा ।
अप्सिर

1

वास्तव में सुंदर नहीं है लेकिन कम से कम ओ (एन) और ओ (1) गुणों को देखना आसान है। मूल रूप से हम सरणी को स्कैन करते हैं और, प्रत्येक संख्या के लिए हम देखते हैं कि क्या इसी स्थिति को पहले से ही देखा गया है (एक बार) (एन) या पहले से ही देखा-एकाधिक-बार (एन + 1)। यदि इसे पहले से ही देखा गया है, तो हम इसे प्रिंट करते हैं और इसे पहले से ही देखे गए कई-कई बार चिह्नित करते हैं। यदि इसे ध्वजांकित नहीं किया जाता है, तो हम इसे पहले से देखे गए-एक बार ध्वजांकित करते हैं और हम संबंधित इंडेक्स के मूल मान को वर्तमान स्थिति में ले जाते हैं (फ़्लैगिंग एक विनाशकारी ऑपरेशन है)।

for (i=0; i<a.length; i++) {
  value = a[i];
  if (value >= N)
    continue;
  if (a[value] == N)  {
    a[value] = N+1; 
    print value;
  } else if (a[value] < N) {
    if (value > i)
      a[i--] = a[value];
    a[value] = N;
  }
}

या, बेहतर अभी तक (तेजी से, डबल लूप के बावजूद):

for (i=0; i<a.length; i++) {
  value = a[i];
  while (value < N) {
    if (a[value] == N)  {
      a[value] = N+1; 
      print value;
      value = N;
    } else if (a[value] < N) {
      newvalue = value > i ? a[value] : N;
      a[value] = N;
      value = newvalue;
    }
  }
}

+1, यह अच्छी तरह से काम करता है, लेकिन इसने यह पता लगाने में थोड़ा विचार किया कि क्यों if (value > i) a[i--] = a[value];काम करता है: यदि value <= iतब हमने पहले से ही मूल्य संसाधित a[value]कर लिया है और इसे सुरक्षित रूप से अधिलेखित कर सकते हैं। इसके अलावा, मैं नहीं कहूंगा कि ओ (एन) प्रकृति स्पष्ट है! इसे बाहर वर्तनी: मुख्य लूप Nकई बार चलता है, साथ ही कई बार a[i--] = a[value];लाइन चलती है। यह रेखा केवल तभी चल सकती है a[value] < N, और जब भी यह चलता है, तो तुरंत बाद में एक सरणी मान जो पहले Nसे सेट नहीं था N, तो यह अधिक से अधिक Nबार, कुल 2Nलूप पुनरावृत्तियों में से एक के लिए चल सकता है ।
j_random_hacker

1

सी में एक समाधान है:

#include <stdio.h>

int finddup(int *arr,int len)
{
    int i;
    printf("Duplicate Elements ::");
    for(i = 0; i < len; i++)
    {
        if(arr[abs(arr[i])] > 0)
          arr[abs(arr[i])] = -arr[abs(arr[i])];
        else if(arr[abs(arr[i])] == 0)
        {
             arr[abs(arr[i])] = - len ;
        }
        else
          printf("%d ", abs(arr[i]));
    }

}
int main()
{   
    int arr1[]={0,1,1,2,2,0,2,0,0,5};
    finddup(arr1,sizeof(arr1)/sizeof(arr1[0]));
    return 0;
}

यह O (n) समय और O (1) स्थान की जटिलता है।


1
इसका स्थान जटिलता O (N) है, क्योंकि यह N अतिरिक्त साइन बिट्स का उपयोग करता है। एल्गोरिथ्म को इस धारणा के तहत काम करना चाहिए कि सरणी तत्व प्रकार केवल 0 से N-1 तक संख्या पकड़ सकता है ।
कैफे

हाँ यह सच है, लेकिन इसके सही के बारे में पूछे जाने पर कि वे केवल 0 से n-1 के लिए संख्या के लिए अहंकार चाहते थे और मैंने आपके समाधान को O (n) से ऊपर जाने की जाँच की, इसलिए मैंने इस बारे में सोचा
अंशुल ने

1

मान लेते हैं कि हम इस सरणी को एक यूनि-दिशात्मक ग्राफ डेटा संरचना के रूप में प्रस्तुत करते हैं - प्रत्येक संख्या एक शीर्ष है और सरणी में इसका सूचकांक ग्राफ के किनारे पर एक और शीर्ष बिंदु की ओर इंगित करता है।

और भी सरलता के लिए हमारे पास 0 से n-1 और 0..n-1 से संख्या की सीमा है। जैसे

   0  1  2  3  4 
 a[3, 2, 4, 3, 1]

0 (3) -> 3 (3) एक चक्र है।

उत्तर: सूचकांकों पर निर्भर सरणी को केवल पीछे छोड़ें। अगर [x] = [a] तो यह एक चक्र है और इस प्रकार डुप्लिकेट है। अगले इंडेक्स पर जाएं और फिर से और आगे तब तक जारी रखें, जब तक कि कोई ऐरे के अंत तक न हो जाए। जटिलता: O (n) समय और O (1) स्थान।


0

ऊपर कैफे विधि प्रदर्शित करने के लिए एक छोटा अजगर कोड:

a = [3, 1, 1, 0, 4, 4, 6] 
n = len(a)
for i in range(0,n):
    if a[ a[i] ] != a[i]: a[a[i]], a[i] = a[i], a[a[i]]
for i in range(0,n):
    if a[i] != i: print( a[i] )

ध्यान दें कि स्वैप को एक ही iमूल्य के लिए एक से अधिक बार होना पड़ सकता है - whileमेरे उत्तर में ध्यान दें ।
कैफे

0

निम्न C फ़ंक्शन में एल्गोरिदम को आसानी से देखा जा सकता है। मूल सरणी को पुनर्प्राप्त करना, हालांकि आवश्यक नहीं है, प्रत्येक प्रविष्टि modulo n लेना संभव होगा ।

void print_repeats(unsigned a[], unsigned n)
{
    unsigned i, _2n = 2*n;
    for(i = 0; i < n; ++i) if(a[a[i] % n] < _2n) a[a[i] % n] += n;
    for(i = 0; i < n; ++i) if(a[i] >= _2n) printf("%u ", i);
    putchar('\n');
}

परीक्षण के लिए Ideone लिंक।


मुझे डर है कि यह तकनीकी रूप से "धोखा" है, क्योंकि 2 * n तक की संख्या के साथ काम करने के लिए मूल संख्याओं को संग्रहीत करने के लिए जो आवश्यक है, उस पर प्रति प्रविष्टि प्रविष्टि के लिए अतिरिक्त 1 बिट स्थान की आवश्यकता होती है। वास्तव में आपको प्रति प्रविष्टि log2 (3) = 1.58 अतिरिक्त बिट्स के करीब की आवश्यकता है, क्योंकि आप 3 * n-1 तक की संख्याएँ संग्रहीत कर रहे हैं।
j_random_hacker

0
static void findrepeat()
{
    int[] arr = new int[7] {0,2,1,0,0,4,4};

    for (int i = 0; i < arr.Length; i++)
    {
        if (i != arr[i])
        {
            if (arr[i] == arr[arr[i]])
            {
                Console.WriteLine(arr[i] + "!!!");
            }

            int t = arr[i];
            arr[i] = arr[arr[i]];
            arr[t] = t;
        }
    }

    for (int j = 0; j < arr.Length; j++)
    {
        Console.Write(arr[j] + " ");
    }
    Console.WriteLine();

    for (int j = 0; j < arr.Length; j++)
    {
        if (j == arr[j])
        {
            arr[j] = 1;
        }
        else
        {
            arr[arr[j]]++;
            arr[j] = 0;
        }
    }

    for (int j = 0; j < arr.Length; j++)
    {
        Console.Write(arr[j] + " ");
    }
    Console.WriteLine();
}

0

मैंने 0 (एन) समय जटिलता और निरंतर अतिरिक्त स्थान में डुप्लिकेट खोजने के लिए स्विफ्ट में एक नमूना खेल का मैदान ऐप बनाया है। कृपया url ढूँढना डुप्लिकेट की जाँच करें

IMP उपरोक्त समाधान तब काम करता है जब किसी सरणी में 0 से n-1 तक के तत्व होते हैं, इनमें से किसी भी संख्या में किसी भी संख्या में कई बार दिखाई देते हैं।


0
private static void printRepeating(int arr[], int size) {
        int i = 0;
        int j = 1;
        while (i < (size - 1)) {
            if (arr[i] == arr[j]) {
                System.out.println(arr[i] + " repeated at index " + j);
                j = size;
            }
            j++;
            if (j >= (size - 1)) {
                i++;
                j = i + 1;
            }
        }

    }

उपरोक्त समाधान O (n) और निरंतर स्थान की समय जटिलता में समान प्राप्त करेगा।
user12704811

3
इस कोड स्निपेट के लिए धन्यवाद, जो कुछ सीमित अल्पकालिक सहायता प्रदान कर सकता है। एक उचित व्याख्या यह दर्शाती है कि समस्या का एक अच्छा समाधान क्यों है, यह दिखा कर इसके दीर्घकालिक मूल्य में बहुत सुधार होगा , और यह भविष्य के पाठकों को अन्य, समान प्रश्नों के साथ और अधिक उपयोगी बना देगा। कृपया कुछ स्पष्टीकरण जोड़ने के लिए अपने उत्तर को संपादित करें, जिसमें आपके द्वारा की गई धारणाएँ शामिल हैं।
टोबी स्पाईट

3
BTW, समय की जटिलता यहाँ O (n here) प्रतीत होती है - आंतरिक लूप को छुपाने से वह परिवर्तित नहीं होता है।
टोबी स्पाईट

-2

यदि सरणी बहुत बड़ी नहीं है तो यह समाधान सरल है, यह टिक करने के लिए समान आकार का एक और सरणी बनाता है।

1 अपने इनपुट सरणी के समान आकार का एक बिटमैप / सरणी बनाएं

 int check_list[SIZE_OF_INPUT];
 for(n elements in checklist)
     check_list[i]=0;    //initialize to zero

2 अपने इनपुट सरणी को स्कैन करें और इसकी गिनती को उपरोक्त सरणी में बढ़ाएं

for(i=0;i<n;i++) // every element in input array
{
  check_list[a[i]]++; //increment its count  
}  

3 अब check_list ऐरे को स्कैन करें और डुप्लिकेट को एक बार प्रिंट करें या कई बार वे डुप्लिकेट किए गए हैं

for(i=0;i<n;i++)
{

    if(check_list[i]>1) // appeared as duplicate
    {
        printf(" ",i);  
    }
}

बेशक, ऊपर दिए गए समाधान द्वारा खपत की गई जगह से दोगुना समय लगता है, लेकिन समय दक्षता ओ (2 एन) है जो मूल रूप से ओ (एन) है।


यह O(1)स्थान नहीं है।
डैनियल कामिल कोजार

उफ़…! ध्यान नहीं दिया ... मेरा बुरा।
दीप

@ निखिल कैसा है ओ (1) ?. मेरा सरणी चेक_लिस्ट रेखीय रूप से बढ़ता है क्योंकि इनपुट का आकार बढ़ता है, तो यह ओ (1) कैसे है यदि ऐसा है तो आप इसे ओ (1) कॉल करने के लिए उपयोग कर रहे हैं।
दीप

किसी दिए गए इनपुट के लिए आपको निरंतर स्थान की आवश्यकता होती है, क्या वह O (1) नहीं है? मैं अच्छी तरह से गलत हो सकता है :)
nikhil

इनपुट बढ़ने पर मेरे समाधान को अधिक स्थान की आवश्यकता है। एल्गोरिथ्म की दक्षता (स्पेस / टाइम) किसी विशेष इनपुट के लिए नहीं मापी जाती है। (ऐसे में हर सर्चिंग एल्गोरिथम की टाइम एफिशिएंसी स्थिर होगी (1 इंडेक्स में पाया गया तत्व जहां हमने खोजा)। किसी भी इनपुट के लिए मापा जाता है। यही कारण है कि हमारे पास सबसे अच्छा मामला है, सबसे खराब मामला और औसत मामला है।
दीप
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.