【问题标题】:Find duplicates in an array, without using any extra space在数组中查找重复项,而不使用任何额外空间
【发布时间】:2011-10-26 16:44:59
【问题描述】:

给定一个包含 n 个整数元素的数组,你将如何在 O(n) 时间内找到数组中是否存在重复项而不使用任何额外的空间。

额外空间意味着 O(n) 阶的额外空间。

Xor 运算符有什么帮助吗?

【问题讨论】:

  • 好吧,你不能使用任何额外的空间和 O(n) 时间。
  • 不使用额外空间是不可能解决这个问题的;您甚至无法保存单个变量的内存来遍历数组!你的意思是在 O(1) 空间中吗?如果是这样,有一些算法,但它们都假设了输入的结构。你还知道数组的哪些其他属性?
  • 我知道 2 种使用基数排序和 Xoring 的方法,但无法解决。如果你知道,请给我概述。
  • 还有更多信息吗?即是否给出了整数范围?
  • 它是impossible,除非你忘了提到一些简化约束

标签: algorithm


【解决方案1】:

如果没有其他信息,这个问题似乎无法解决,因为这是Element Distinctness Problem,在您提供的限制下,在要求的时间内无法解决。

你可以允许:

(1) 更多内存 并使用 hashtable / hashset 并满足 O(n) 时间标准。 [迭代数组,检查一个元素是否在哈希表中,如果是你有欺骗,否则 - 将元素插入表中并继续]。

(2)更多时间,对数组[O(nlogn)]进行排序,满足次线性空间条件。 [排序后,遍历数组,对于每个 a[i] , a[i+1] ,检查它们是否相同。如果您没有找到相同的配对,则说明您没有受骗]

编辑:这个声明的证明有点冗长,并且需要这里不支持的数学符号(旁注:我们确实需要 tex 支持),但想法是如果我们模拟我们的问题作为代数计算树(当不允许散列时这是一个公平的假设,并且可以使用恒定空间),然后,Ben Or 在他的文章 Lower Bounds For Algebraic Computation Trees (1983)(发表在著名的 ACM 中)中证明了元素的区别是 Omega(nlogn)这个模型下的问题。 Lubiw 表明,在 1991 年将我们限制为整数时,同样的结论也适用:A Lower Bound for the Integer Element Distinctness Problem,但这些文章得出的结论是,在代数树计算模型下 - Integer Distinctness Problem is Omega(nlogn) Problem

【讨论】:

  • 但这在 O(1) 空间或 O(n) 时间内不起作用 - 您需要 O(n) 辅助存储空间用于 LSD 基数排序或 O(lg n) 空间用于MSD 基数排序。
  • @templatedtypedef:他的问题表明他不需要 O(1) 额外空间,但亚线性额外空间:With extra space it means extra space of order O(n),它表明亚线性是可以接受的
  • 正好是它的 O(n*m),其中 m=最大元素中的位数。
  • 因为显然不是 OP 正在寻找的,我从答案中删除了基数排序选项。我把它留在里面了,如何用更多的空间或更多的时间来完成。
  • @amit 你的意思不是哈希集而不是哈希表吗?
【解决方案2】:

就地基数排序,然后是线性扫描

In place radix sort algorithm

根据您实际考虑的基数排序的时间复杂度,这个解决方案是 O(N) 时间,尽管我个人的看法并非如此。我认为如果你不对整数排序做线性时间假设,那么这个问题是无解的。

由于排序是就地的,因此只需要 O(1) 的额外存储空间。

代码都是 C++11

第 1 步:就地基数排序

template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void RecurseOnRadixSort(std::vector<T>& myArray, T mask, int zerosEnd, int onesBegin)
{
    if (zerosEnd+1 >= onesBegin-1 || mask == 0) 
        return;

    int zerosEnd2 = zerosEnd;
    int onesBegin2 = onesBegin;
    while(zerosEnd2+1 <= onesBegin2-1)
    {
        // swap ones to the right
        if ((myArray[zerosEnd2+1] & mask) != 0)
        {
            std::swap(myArray[zerosEnd2+1], myArray[onesBegin2-1]);
            --onesBegin2;
        }
        else
            ++zerosEnd2;
    }

    mask >>= 1;

    //recurse on lhs
    RecurseOnRadixSort(myArray, mask, zerosEnd, zerosEnd2+1);

    //recurse on rhs
    RecurseOnRadixSort(myArray, mask, onesBegin2-1, onesBegin);
}

template <typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void InPlaceRadixSort(std::vector<T>& myArray)
{
    int zerosEnd = -1;
    int onesBegin = static_cast<int>(myArray.size());
    T mask = static_cast<T>(1) << sizeof(T)*8-1;
    while(zerosEnd+1 <= onesBegin-1)
    {
        if ( (myArray[zerosEnd+1] & mask) != 0)
        {
            std::swap(myArray[zerosEnd+1], myArray[onesBegin-1]);
            --onesBegin;
        }
        else
            ++zerosEnd;
    }

    mask = static_cast<T>(1) << sizeof(T)*8-2; // need to reassign in case of signed datatype
    //recurse on lhs
    RecurseOnRadixSort(myArray, mask, -1, zerosEnd+1);
    //recurse on rhs
    RecurseOnRadixSort(myArray, mask, onesBegin-1, static_cast<int>(myArray.size()));

    // swap negatives to the front
    auto iterSmallest = std::min_element(myArray.begin(), myArray.end());
    if (*iterSmallest < 0)
    {
        std::reverse(myArray.begin(), myArray.end());
        iterSmallest = std::min_element(myArray.begin(), myArray.end());
        std::reverse(myArray.begin(), iterSmallest+1);
        std::reverse(iterSmallest+1, myArray.end());
    }
}

第 2 步:重复元素的线性扫描

for (size_t i=0, j=1; j<myArray.size(); ++i,++j)
{
    if (myArray[i] == myArray[j])
    {
        std::cout << "Found duplicate element " << myArray[i];
    }
}

完整代码

#include <iostream>
#include <string>
#include <vector>
#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>
#include <type_traits>
using namespace std;
#define N 10

template <typename T>
void PrintArray(const std::vector<T>& myArray)
{
    for (auto&& element : myArray)
    {
        std::cout << element << std::endl;
    }
}

template<typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void RecurseOnRadixSort(std::vector<T>& myArray, T mask, int zerosEnd, int onesBegin)
{
    if (zerosEnd+1 >= onesBegin-1 || mask == 0) 
        return;

    int zerosEnd2 = zerosEnd;
    int onesBegin2 = onesBegin;
    while(zerosEnd2+1 <= onesBegin2-1)
    {
        // swap ones to the right
        if ((myArray[zerosEnd2+1] & mask) != 0)
        {
            std::swap(myArray[zerosEnd2+1], myArray[onesBegin2-1]);
            --onesBegin2;
        }
        else
            ++zerosEnd2;
    }

    mask >>= 1;

    //recurse on lhs
    RecurseOnRadixSort(myArray, mask, zerosEnd, zerosEnd2+1);

    //recurse on rhs
    RecurseOnRadixSort(myArray, mask, onesBegin2-1, onesBegin);
}

template <typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void InPlaceRadixSort(std::vector<T>& myArray)
{
    int zerosEnd = -1;
    int onesBegin = static_cast<int>(myArray.size());
    T mask = static_cast<T>(1) << sizeof(T)*8-1;
    while(zerosEnd+1 <= onesBegin-1)
    {
        if ( (myArray[zerosEnd+1] & mask) != 0)
        {
            std::swap(myArray[zerosEnd+1], myArray[onesBegin-1]);
            --onesBegin;
        }
        else
            ++zerosEnd;
    }

    mask = static_cast<T>(1) << sizeof(T)*8-2; // need to reassign in case of signed datatype
    //recurse on lhs
    RecurseOnRadixSort(myArray, mask, -1, zerosEnd+1);
    //recurse on rhs
    RecurseOnRadixSort(myArray, mask, onesBegin-1, static_cast<int>(myArray.size()));

    // swap negatives to the front
    auto iterSmallest = std::min_element(myArray.begin(), myArray.end());
    if (*iterSmallest < 0)
    {
        std::reverse(myArray.begin(), myArray.end());
        iterSmallest = std::min_element(myArray.begin(), myArray.end());
        std::reverse(myArray.begin(), iterSmallest+1);
        std::reverse(iterSmallest+1, myArray.end());
    }
}

int main() {
    srand(time(NULL));
    std::vector<int> myArray(N);
    for (size_t i=0;i<myArray.size();++i)
    {
        myArray[i] = rand() % 100 * (rand() % 2 == 1?-1:1);
    }

    std::cout << "Vector before radix sort: " << std::endl;
    PrintArray(myArray);
    InPlaceRadixSort(myArray);
    std::cout << "Vector after radix sort: " << std::endl;
    PrintArray(myArray);

    for (size_t i=0, j=1; j<myArray.size(); ++i,++j)
    {
        if (myArray[i] == myArray[j])
        {
            std::cout << "Found duplicate element " << myArray[i];
        }
    }
    return 0;
}

Live Demo

【讨论】:

  • 我同意您的解决方案,但想对基数排序添加一些见解。基数排序的时间复杂度为 O(N),因为它具有由元素的位宽给出的隐式上限。更准确地说,它是 O(N*bitwidth) 其中 bitWidth == sizeof(T) * 8 参考您的代码。因此,对于N &lt;= exp2(bitWidth): bitWidth &gt;= log2(N)。因此,对于N &lt; exp2(bitwidth),基数排序的时间复杂度比 NlogN 差。
  • @stephan,谢谢。此外,人们会在所有重复元素的列表上看到最坏情况的时间复杂度,即使它在技术上是排序的!
【解决方案3】:

这里有一个有趣的solution 来解决这个问题,它有一个单一的约束条件,即元素的范围应在 0 到 n-2(含)之间,其中 n 是元素的数量。

这在 O(n) 时间内工作,空间复杂度为 O(1)。

【讨论】:

    【解决方案4】:

    这是使用 O(n) 时间和 O(1) 空间使用的解决方案!

    Traverse the array. Do following for every index i of A[].
    {
        check for sign of A[abs(A[i])] ;
        if positive then        make it negative by   A[abs(A[i])]=-A[abs(A[i])];
        else  // i.e., A[abs(A[i])] is negative
        this   element (ith element of list) is a repetition
    }
    

    致谢:方法 5 Geek for Geeks

    【讨论】:

    • 如果我的数组是 10,20,10,30。抛出越界异常
    • 如果数组中的值大于数组的大小,将不起作用。
    • 此解决方案仅适用于元素在某个范围内,例如 0 到 n-1。它不适用于一般未绑定的数字
    • 您提供的链接是关于另一个问题的。
    • 这将需要为每个元素增加一个位,因此 O(n)。
    【解决方案5】:

    此解决方案基于 @dsimcha 从数组中删除重复项的解决方案,如 here 所示。

    它执行就地交换算法,使用值散列来交换位置。请注意,这会在一定程度上破坏原始数组内容。但是 OP 的问题中没有要求禁止这样做。

    public static class DupFinder
    {
        public static bool HasDups(int[] array, ref int nEvals)
        {
            nEvals = 0;
            return DupFinder.FindInPlace(array, 0, ref nEvals);
        }
    
        private static bool FindInPlace(int[] array, int start, ref int nEvals)
        {
            if (array.Length - start < 2)
                return false;
    
            var sentinel = array[start];
            var offset = start + 1;
            var len = array.Length - offset;
            for (var ndx = 0; ndx < len; nEvals++)
            {
                var cur = array[offset + ndx];
                if (cur == sentinel)
                {
                    ndx++;
                    continue;
                }
    
                var hash = cur % len;
                if (ndx == hash)
                {
                    ndx++;
                    continue;
                }
    
                var at_hash = array[offset + hash];
                if (cur == at_hash)
                {
                    array[offset + ndx] = sentinel;
                    ndx++;
                    continue;
                }
    
                if (at_hash == sentinel)
                {
                    Swap(array, offset, ndx, hash);
                    ndx++;
                    continue;
                }
    
                var hash_hash = at_hash % len;
                if (hash_hash != hash)
                {
                    Swap(array, offset, ndx, hash);
                    if (hash < ndx)
                        ndx++;
                }
                else
                {
                    ndx++;
                }
            }
    
            var swapPos = 0;
            for (var i = 0; i < len; i++, nEvals++)
            {
                var cur = array[offset + i];
                if (cur != sentinel && i == (cur % len))
                    Swap(array, offset, i, swapPos++);
            }
    
            for (var i = swapPos; i < len; nEvals++)
            {
                var cur = array[offset + i];
                if (cur == sentinel)
                    return true; // got dups.
                else
                    i++;
            }
    
            // Let's assume C# supports tail recursion ;-)
            // Then => look ma, O(1) extra storage space.
            return FindInPlace(array, offset + swapPos, ref nEvals);
        }
    
        private static void Swap(int[] array, int offset, int first, int second)
        {
            var tmp = array[offset + first];
            array[offset + first] = array[offset + second];
            array[offset + second] = tmp;
        }
    }
    

    因此,如果我们暂时假设 c# 支持尾递归并且我们不将使用的堆栈帧计为额外空间,那么它有 O(1) 的空间需求。

    作者提到它的时间复杂度为 O(N)-ish。我执行的(有限)测试(与计算复杂性分析相反)表明它更接近 O(N log N)。

    Array Size   Dup Position    #Evals
    12           7               26
    12           -               35
    100,000      80,000          279,997
    100,000      -               453,441
    

    【讨论】:

      【解决方案6】:

      对于一般情况,由于复杂性强约束和无限制的输入,这个问题似乎没有解决方案。

      很明显,您至少需要 N 步才能看到所有输入。所以它不可能比O(n)

      现在,为了确保发现每一个可能的重复项,您有不同的可能性:

      • 将每个数字与其他数字进行比较,这不需要太多额外空间,但需要O(n^2)时间`
      • 通过在可用空间中交换整数,以更智能的方式进行比较。这允许在序列本身中“存储信息”。实际上,将所有数字相互比较通常是在排序算法中完成的。已知最快的不需要额外空间的排序算法需要O(n log n) 时间。 Wikipedia has a rather lengthy writeup with lots of sources。因此,您永远无法以这种方式满足您的时间要求。 (some comparison chart of known sorting algorithms)
      • 您可以使用哈希映射进行一些记账,这可能只允许您花费线性时间O(n),但需要将记账存储在某处。否则,您将简单地“忘记”您已经看到的数字。不幸的是,如果您的输入增加,簿记将需要更多空间,因为您要记住很多不同的数字。所以不可能有 same 固定的内存量并比较任意长的输入序列。因此,您将不得不违反常量空间O(1)

      正如@Atishay 在他的回答中指出的那样,如果您的输入非常有限,可以找到解决方案。这里要求您有一个大小为n 的数组,并且可能的值仅在[0,n-2] 范围内。这个要求保证了在某处必须有重复,因为数组中的不同值比元素少。有了这些知识和非常具体的价值观范围,你就可以做到。但这使用了非常狭窄的假设,并不能解决问题中所述的一般问题。

      编辑

      正如 cmets 中所阐明的,基于比较的排序算法的时间复杂度有一个已证明的下限。供参考,请参见此处:

      【讨论】:

      • 很好的总结。另外:基于比较的排序被证明在小于 O(n*lgn) 的情况下是不可能的。尽管如此,我们仍不能确定不会有类似于 [0..n-2] 解决方案的算法使用 O(1) 额外空间和 O(n) 额外时间。
      • 谢谢,我添加了 2 个支持您提及已证明的下限的参考资料。你是对的,目前没有正式的结果表明一般问题的复杂性较低。
      【解决方案7】:

      Bloom filter 是一个节省空间的哈希集,具有可调的误报率。误报的可能性意味着当您从 BF 中受到打击时,您必须返回并检查真正的重复项,引入 N^2 项 - 但系数是 ~exp(-(用于过滤器的额外空间))。这产生了一个有趣的空间与时间权衡空间。

      我没有证据证明提出的问题是不可解决的,但总的来说,“这里有一个有趣的权衡空间”是解决不可解决问题的好答案。

      【讨论】:

        【解决方案8】:

        使用单个 int 作为临时变量的实现.. 这是使用位向量/

         public static boolean isUniqueChars(String str) {
            int checker = 0;
            for (int i = 0; i < str.length(); ++i) {
             int val = str.charAt(i) - ‘a’;
             if ((checker & (1 << val)) > 0) return false;
             checker |= (1 << val);
            }
            return true;
          }
        

        或者我之前的 O(n^2) 实现而不使用任何临时变量

        public static bool isDuplicate(char[] str) {
            if (str == null) return false;
            int len = str.length;
            if (len < 2) return false;
        
            for (int i = 1; i < len; ++i) {
              for (int j = 0; j < len; ++j) {
                if (str[i] == str[j]) return true;
              }
            }
            return false;
          }
        

        【讨论】:

        • OP 不是在询问字符,而是在询问整数,这个解决方案假设最多有 2^32 个可能的值。 【当元素范围非常有界时,其实就是这个问题的hash解】。
        【解决方案9】:

        干净的例子来确定时间 O(n) 和空间 O(1) 的重复:

        public class DuplicateDetermineAlgorithm {
            public static boolean isContainsDuplicate(int[] array) {
                if (array == null) {
                    throw new IllegalArgumentException("Input array can not be null");
                }
                if (array.length < 2) {
                    return false;
                }
        
                for (int i = 0; i < array.length; i++) {
                    int pointer = convertToPositive(array[i]) - 1;
                    if (array[pointer] > 0) {
                        array[pointer] = changeSign(array[pointer]);
                    } else {
                        return true;
                    }
                }
                return false;
            }
        
            private static int convertToPositive(int value) {
                return value < 0 ? changeSign(value) : value;
            }
        
            private static int changeSign(int value) {
                return -1 * value;
            }
        }
        

        【讨论】:

          【解决方案10】:
          public static void getDuplicatesElements (Integer arr[]){
          
              //Status array to track the elements if they are already considered
              boolean status[] = new boolean [arr.length];
          
              //Flag to mark the element found its duplicate
              boolean dupFlag = false;
          
              //Output string
              String  output = "";
          
              //Count of duplicate elements found
              int count = 0;
          
              //Initialize status array with all false i.e. no duplicates
              for (int i = 0; i < arr.length; i++)
              {
                  status[i] = false;
              }
          
              //first loop to check every element
              for (int i = 0; i < arr.length - 1; i++)
              {
                  //Initialize every element to no duplicate
                  dupFlag = false;
          
                  //Check if this element is not already found duplicate, if not, check now.
                  if (!status[i]){
                      for (int j = i+1; j <  arr.length; j++){
                          if (arr[i] == arr[j]){
                              dupFlag = true;
                              status[j] = true;
                          }
                      }
                  }
          
                  if (dupFlag){
                      output = output + " " + arr[i];
                      count++;
                  }
              }
          
              System.out.println("Duplicate elements: " + output );
              System.out.println("Count: " + count );
          
          }
          

          【讨论】:

            【解决方案11】:

            免责声明

            我没有答案,但我的想法太广泛了,无法发表评论。另外,我想把它们写下来,所以我花在思考解决方案上的三个小时不会完全浪费掉。我希望给你一个不同的观点,但如果你不喜欢浪费你的时间,请不要继续阅读。或者只是对这个答案投反对票,这是值得的:)

            为了启动我们的视觉思维,让我们有一个示例数组:50 100 150 -2 -1 0 1 2 3 4。你肯定知道,它没有重复,所以我们的算法应该输出FALSE。另外,它的长度是10

            步骤 A:以 O(N) 时间计数

            让我们暂时忽略额外的内存限制(实际上,非常严重地违反它,假设我们可以拥有O(\inf) 额外的内存:) 并保存在一个虚构的无限数组中(它也是双重无限的,因为它允许负indeces 也)每个整数的计数。对于我们的输入,这个数组看起来像这样:

            ...000001111111000...00100...00100...001000000...
                    ^              ^               ^
               [index  -2]     [index  50]     [index 150]
            

            如果数组中的任何元素大于1,那么我们就有重复,算法应该返回TRUE

            步骤 B:在 O(N) 时间内将 -inf..inf 映射到 0..N

            假设我们有一个映射f(x):-inf..inf -&gt; 0..N,它可以将我们的无限数组压缩为一个大小为 N 的数组,并且在 O(N) 时间内完成。这就是散列的理想做法。请注意,我们不关心维护数组的顺序,因为我们只关心它是否具有大于 1 的元素。因此,我们可以将这两个步骤结合起来,并消除对无限内存的需求 - 耶!我们仍在使用额外的 O(N) 内存(实际上,正好是 N 个计数)来保存计数值。下一步将摆脱它。

            步骤 C:使用第一个元素作为开关

            在我解释这一步之前,请注意我们实际上并不需要存储任何大于 1 的计数。当我们第一次想要增加一个计数器时,我们注意到它的值已经是 1,我们知道我们发现了一个重复!所以每个计数器 1 位内存就足够了。这将所需的内存减少到 O(lg(N)),但我们并不真正关心这一点,因为它还不够好。重要的是每个计数器 1 位内存就足够了。

            我们现在将利用我们可以修改输入数组的事实。我们遍历数组并xor 具有第一个元素的值的所有元素。如果结果小于操作前的值,我们将其更改为该结果。我们还将第一个元素单独存储为sw,额外的内存成本为 O(1)。

            现在,我们可以使用存储的第一个元素 sw 和转换后的数组以以下方式对计数步骤(步骤 A + B)的计数进行编码:考虑具有索引 k 的 @987654333 的元素@,如果A[f(A[k])] &lt; A[f(A[k])] xor sw,那么计数是zero,这意味着我们正在考虑的元素-A[k]-以前没有见过,所以我们将A[f(A[k])]更改为A[f(A[k])] xor sw。如果,否则,A[f(A[k])] &gt; A[f(A[k])] xor sw,则计数为one,这意味着我们正在考虑的元素 - A[k] - 之前已经见过,所以它是重复的。

            假设地图:

            f(-2 xr 50) -> 0
            f(-1 xr 50) -> 1
            f(0)        -> 2
            f(1)        -> 3
            f(2)        -> 4
            f(3)        -> 5
            f(4)        -> 6
            f(86)       -> 7
            f(150)      -> 8
            f(1337)     -> 9
            

            在按以下顺序执行步骤后:step c; step a+b 输入数组如下所示:

            50(0) 100(86) 150(164) -2(-2 xr 50) -1(-1 xr 50) 0(50) 1(51) 2(48) 3(49) 4(54) [intermediate state, not stored in memory]
            0     86      150      -2 xr 50     -1 xr 50     0     1     2     3     4     [state after step c]
            0     86     *164*     -2 xr 50     -1 xr 50     0     1     2     3     4     [counted element 0]
            0     86      164      -2 xr 50     -1 xr 50     0     1    *48*   3     4     [counted element 1]
            0     86      164      -2 xr 50     -1 xr 50     0     1     48   *49*   4     [counted element 2]
            *50*  86      164      -2 xr 50     -1 xr 50     0     1     48    49    4     [counted element 3]
            50   *100*    164      -2 xr 50     -1 xr 50     0     1     48    49    4     [counted element 4]
            50    100    !164!     -2 xr 50     -1 xr 50     0     1     48    49    4     [counted element 5]
            

            尝试计算索引为5(即0)的元素,我们看到数组中已经有一个0! (因为A[f(A[5])]164大于164 xr 50)所以我们输出TRUE,算法结束。

            故事的寓意

            如果我们没有足够的权限memory x time,我们一定会忘记一些事情并犯错误。

            对不起

            不幸的是,我们没有完美的哈希函数,而且我们不能凭空创建内存,因此传统方法在所需的约束下无法工作。 OP 给出的答案指向的算法可能会被修改,以便它允许使用解释为数组索引的数字将落在数组边界之外,给定一个完美的散列函数。但即便如此,也必须发明如何使用它来检测重复,而不是找到一个保证存在的...

            无论如何,有趣的问题。

            【讨论】:

              【解决方案12】:

              我想提出以下解决方案。我希望它可以帮助。

              assert(A.size() > 1);
              
              int m = A[0];
              for(unsigned int i = 1; i < A.size(); ++i) {    // O(n)
                  m ^= A[i];
                  m ~= m;
              }
              return m;
              

              它基于:https://codesays.com/2015/solution-to-odd-occurrences-in-array-by-codility/

              干杯

              【讨论】:

                【解决方案13】:

                您可以使用交换排序进行所有必要的交换并最终找到重复的元素。如果您想在同一个数组中找到多个重复元素,您也可以扩展此解决方案。 此解决方案的一个警告是数组中数字的范围必须是从 1 到 n。

                下面是示例代码。 输入 --> [1,3,4,2,2] 这里重复元素是 2。 您可以针对上述输入运行此代码。

                    public int findDuplicate(int[] nums) {
                
                    int i = 0;
                    while(i < nums.length){
                        if(nums[i] != nums[nums[i] - 1]){
                            swap(i, nums[i] - 1, nums);
                        }else{
                            i++;   
                        }
                    }
                    for(int j=0; j < nums.length; j++){
                        if(nums[j] != j + 1){
                            return nums[j];
                        }
                    }
                    return -1;
                }
                    public static void swap(int i, int j, int[] nums){
                        int temp = nums[i];
                        nums[i] = nums[j];
                        nums[j] = temp;
                }
                

                必须牢记的主要事情是索引 i 应该包含 i + 1 个数字。 例如:索引 0 应该有元素 1,即 arr[0] = 1,索引 1 应该有元素 2,即 arr[1] = 2,依此类推。

                这个方案不需要额外的空间,时间复杂度是O(n)

                【讨论】:

                  【解决方案14】:
                  import java.util.HashSet;
                  import java.util.Set;
                  
                  
                  public class FindDups {
                  public static void main(String[] args) {
                      int a[]={1,2,3,3,4};
                  
                      Set<Integer> s=new HashSet<Integer>();
                      for(int i=0;i<a.length;i++)
                      {
                      if(!s.add(a[i]))
                          System.out.println("at index"+ i+" "+a[i]+"is duplicate");  
                      }
                      for(int i:s)
                      {
                          System.out.println(i);
                      }
                  }
                  }
                  

                  【讨论】:

                  • 只是转储一些代码并不能解决问题,您应该解释一下您的代码在做什么以及它如何满足问题的空间和时间限制
                  • 你正在使用额外的空间——哈希集。
                  猜你喜欢
                  • 2011-06-21
                  • 2018-08-19
                  • 2017-12-06
                  • 2017-04-06
                  • 2022-01-15
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多