【问题标题】:find the first duplicate element in an array having the minimal index在具有最小索引的数组中找到第一个重复元素
【发布时间】:2018-11-07 22:51:54
【问题描述】:

我有一个包含一些重复元素的数组,如下所示:

找到第二次出现的索引最小的第一个重复数字。换句话说,如果有超过 1 个重复的数字,则返回第二次出现的索引比另一个数字的第二次出现的索引更小的数字。如果没有这样的元素,返回 -1

对于 a = [2, 1, 3, 5, 3, 2],输出应该是 firstDuplicate(a) = 3.

有 2 个重复项:数字 2 和 3。第二次出现的 3 比第二次出现的 2 的索引更小,所以答案是 3。

我试过了:

int firstDuplicate(int[] a) {
  Set<Integer> set = new HashSet<>();
  Map<Integer, Integer> hm = new HashMap<Integer,Integer>();
  Map.Entry<Integer, Integer> min = null;
  for(int i=0;i<a.length;i++){
       // if(!hm.containsKey(a[i]))
              hm.put(a[i],i);
  }
  for(Map.Entry<Integer,Integer> entry : hm.entrySet()){
        if(min == null || entry.getValue() < min.getValue()){
              min = entry;
        }
  }
  return min == null ? new Integer(-1) : min.getKey();

}

没有成功,但我在网上找到了另一个解决方案,如下所示:

int firstDuplicate(int[] a) {
  Set<Integer> set = new HashSet<>();
  Map<Integer, Integer> hm = new HashMap<Integer,Integer>();
  Map.Entry<Integer, Integer> min = null;
  for(int i=0;i<a.length;i++){
        if(set.add(a[i])==false && !hm.containsKey(a[i]))
              hm.put(a[i],i);
  }
  for(Map.Entry<Integer,Integer> entry : hm.entrySet()){
        if(min == null || entry.getValue() < min.getValue()){
              min = entry;
        }
  }
  return min == null ? new Integer(-1) : min.getKey();

}

谁能在这里解释一下 Hashset 的使用,因为它不允许重复,所以 if 条件如何可行。

【问题讨论】:

  • 它基本上计算一个哈希图,其键是元素,值是其副本的索引。然后它计算在 hashmap 中的最小索引搜索。
  • Set.add 将返回 false 如果集合中已经包含您要添加的对象。因此,通过检查返回值是否为false,您可以检查对象是否已经在集合中。如果它还没有出现在集合中(并且是第二次出现,而不是以后出现),则将其位置保存在地图中。这会生成一个包含所有重复项及其首次出现的地图。现在,您只需查看哪个是第一个双倍就完成了。您可以通过一些流逻辑大大缩短此代码,但老实说,或者仅返回第一个 false,因为这显然是最早的重复...

标签: java arrays hashset


【解决方案1】:
int firstDuplicate(int[] a){
int n = a.length;
for(int i=0; i<n; i++)
   {
    if(a[Math.abs(a[i])-1]<0) return Math.abs(a[i]);
    else a[Math.abs(a[i])-1] = - a[Math.abs(a[i])-1];
   }
return -1;
}

我将解释这个为什么以及如何工作。 这个约束很重要:1 ≤ a[i] ≤ a.length 存在,这意味着在这样的数组中:a = [2,8,2] 这个算法将不起作用,因为 8 更大 比 a.length 在这种情况下 3

你也可以找到here的解释:

哈希图

此解决方案遵循哈希图的思想。另一个结构,您可以在其中计算 hash[arr[i]-1]++ 数组中任何给定索引 i 的出现次数。示例:

如果您有 arr[2,1,3,5,3,2],则哈希映射将从 6 个零数组开始:hashmap[0,0,0,0,0,0],因为这是 arr 的大小。随着算法的进展,它将在arr[i]-1 的位置求和+1。它使用该值作为总和的索引。最后你会得到:arr[1,2,2,0,1,0]

这具有 O(n) 的时间复杂度,因为它运行完整的 arr,并且 O(n) em> 及时,因为它至少运行了 1 次数组。

没有哈希图

上述算法的思想是不需要hashmap的额外结构,但可以使用相同的数组来计算频率。这可能会导致问题。让i-th 元素为a 或(arr[i]=a),则计数应存储在arr[arr[i]-1] 或(arr[a-1]),但存储频率时,该元素将丢失。

示例迭代:

  1. a[2,1,3,5,3,2] -> a[2,1,3,5,3,2]
  2. a[2,1,3,5,3,2] -> a[1,1,3,5,3,2]
  3. a[1,1,3,5,3,2] -> a[1,1,1,5,3,2]
  4. a[1,1,1,5,3,2] -> a[1,1,1,5,1,2] 当我们读取5 时,我们丢失了3 的值,因为它将频率存储在arr[arr[4]-1] 或(arr[5-1])。

解决缺失的问题

为了解决这个问题,我们首先将第 i 个元素替换为 arr[arr[i]-1] 或 (arr[a-1]),然后将 -1 放在数组 arr[arr[i]-1] 或 (arr[a-1]) 中。 算法:

  1. 从头到尾遍历数组。
  2. 对于每个元素,检查元素是否小于或等于零。如果为负数或零,则按频率跳过元素。
  3. 如果元素 (a = arr[i] – 1) 为正,则检查 arr[a] 是否为正。如果为正,则表示它是数组中第一次出现a,并将arr[i] 替换为arr[a],并分配arr[a] = -1。如果arr[a] 为负数,则它不是第一次出现,则将arr[a] 更新为arr[a]--,并将arr[i] 更新为arr[i] = 0。您使用一个辅助值来保存 arr[a],该值将在下一次迭代中使用。
  4. 再次,遍历数组并打印 i+1 作为值和 arr[i] 作为频率。

示例迭代:

  1. a[2,1,3,5,3,2] -> a[1,1,3,5,3,2] -> a[1,-1,3,5,3,2]
  2. a[1,-1,3,5,3,2] -> a[1,-1,3,5,3,2] -> a[1,-1,-1,5,3,2]
  3. a[1,-1,-1,5,3,2] -> a[1,-1,-1,0,3,2]
  4. a[1,-1,-1,0,3,2] -> a[1,-1,-1,0,-1,2] -> a[1,-1,-2,0,-1,2]
  5. a[1,-1,-2,0,-1,2] -> a[1,-1,-2,0,-1,0]
  6. a[1,-1,-2,0,-1,0] -> a[1,-2,-2,0,-1,0]

firstDuplicate

了解了这一点后,我们现在可以了解firstDuplicate 的工作原理。这个想法不是计算频率,而是只打印频率已经为负数的index。当我们得到一个负频率时,我们返回。 所以运行我们得到的算法:

  1. 对于if(a[2-1]&lt;0)if(1&lt;0),此比较在arr[arr[0]-1] 或(arr[1]) 和0 之间,所以我们不返回。 a[2,1,3,5,3,2] -> a[2,-1,3,5,3,2]
  2. if(a[1-1]&lt;0)if(2&lt;0) 我们不会返回 a[2,-1,3,5,3,2] -> a[-1,-1,3,5,3,2]
  3. if(a[3-1]&lt;0)if(3&lt;0) 我们不会返回。 a[-1,-1,3,5,3,2] -> a[-2,-1,-3,5,3,2]
  4. if(a[5-1]&lt;0)if(3&lt;0) 我们不会返回。 a[-2,-1,-3,5,3,2] -> a[-2,-1,-3,5,-3,2]
  5. if(a[3-1]&lt;0)if(-3&lt;0) 我们返回。

所有这些都是基于element-1是索引的想法。

【讨论】:

    【解决方案2】:
    public static void main(String[] args){
        int array[]={2, 1, 3, 5, 3, 2};
        int tempArray[]=new int[array.length];
        int index=0;
        while(index< array.length){
            if(++(tempArray[array[index]])==2) 
                break;
            index++;
        }
        if(index> array.length){
            System.out.println("No Duplicate");
        }else {
            System.out.println("First Duplicate " + array[index]);
        }
    }
    

    使用计数排序,甜蜜而简单:)

    【讨论】:

      【解决方案3】:
      int firstDuplicate(int[] a)
          {
              int DupIndex = 0;
              int DupValue = 0;
              for (int i = 0; i < a.Length; i++)
              {
                  for (int j = i + 1; j < a.Length; j++)
                  {
                      if (a[i] == a[j])
                     {
                          if (j < DupIndex)
                          {
                              DupIndex = j;
                              DupValue = a[i];
                          }
                          else if (DupIndex == 0)
                          {
                              DupIndex = j;
                              DupValue = a[i];
                          }
                      }
                  };
              };
              return (DupValue == 0) ? -1 : DupValue;
          }
      

      【讨论】:

        【解决方案4】:

        我强烈建议您尝试这样做以获得正确的结果

        你可以让它更高效的时间复杂度 O(n)

        int firstDuplicate(int[] a){
        int n = a.length;
        for(int i=0; i<n; i++)
        {
           if(a[Math.abs(a[i])-1]<0) return Math.abs(a[i]);
           else a[Math.abs(a[i])-1] = - a[Math.abs(a[i])-1];
        }
        return -1;
        }
        

        【讨论】:

        • 不错,它具有 O(n) 时间和 O(1) 空间复杂度,因此比公认的答案更有效。
        • 这不是一个通用的解决方案。仅当a 中的所有元素都大于1 且最多为a.length 时才有效。
        • 有人能解释一下这个解决方案背后的想法吗?
        【解决方案5】:

        有两种方法可以解决这个问题,使用时间复杂度为 o(n) 的 HashSet 和使用嵌套循环 o(n2)

        for(int i = 0; i < a.length; i++){
            for(int j = i +1; j < a.length; j++){
                   if(a[i] == a[j]){
                           System.out.println(a[i]);
                           return;
                   }
             }
         }
        

        或者你可以让它更有效的时间复杂度 O(n)

        int index -1;
        Set<Integer> hashSet = new HashSet<Integer>();
        for(int i = a.length-1; i >= 0; i--){
             if(hashSet.contains(a[i])){
                   index = i;
             }else{
                   hashSet.add(a[i]);
             }
        }
         System.out.println(a[index]);
        

        【讨论】:

          【解决方案6】:

          您可以将 java 8 与 lambda 和流一起使用。

          这是一行代码:

          Set<Integer> allItems = new HashSet<>();
          Arrays.stream(a).filter(i -> !allItems.add(i)).findFirst().orElse(-1)
          

          它返回你所期望的

          【讨论】:

            【解决方案7】:

            您第一次尝试失败的原因是您将数组元素作为键添加到 Map 而不检查它们是否已经存在,这意味着您在完成填充时无法知道是否有任何重复Map.

            您找到的替代代码做了一些不同的事情。它使用Set 来确定当前数组元素是否已经出现在数组的前面,如果是这种情况,它会将其作为键添加到Map,仅当它不存在时。这意味着Map 将只包含在数组中出现多次的元素,并且与每个元素关联的索引是第一个重复的出现。 IE。对于数组{2, 1, 3, 5, 3, 2}Map 将包含{2=5, 3=4}。然后它将返回具有最小值的键(对应于第一个副本的索引)。

            但是,Map 是不必要的,因为您只需要找到一个重复项,而不是全部。使用Set 定位第一个副本并将其返回:

            int firstDuplicate(int[] a) 
            {
                Set<Integer> set = new HashSet<>();
                for(int i=0;i<a.length;i++){
                    if(!set.add(a[i])) {
                        return a[i];
                    }
                }
                return -1; // no duplicates found 
            }
            

            这依赖于 set.add() 返回 false 如果 Set 已经包含您要添加的元素。一旦它第一次返回false,您就找到了第一个重复项。

            【讨论】:

            • 此代码不返回索引最小的第一个副本。它返回第一个副本。对于问题中提出的示例,它返回 2 而不是 3
            • @DavideLorenzoMARINO 不,它将返回 3,因为数组的第二个 3 将在第二个 2 之前遇到。
            猜你喜欢
            • 2018-09-01
            • 1970-01-01
            • 2017-12-20
            • 1970-01-01
            • 2011-08-29
            • 1970-01-01
            • 2021-08-23
            • 1970-01-01
            相关资源
            最近更新 更多