Поиск элемента в массиве, где каждый элемент повторяется нечетное количество раз (но более одного раза), и только один появляется один раз - PullRequest
24 голосов
/ 07 сентября 2011

У вас есть массив, в котором каждое число повторяется нечетное количество раз (но более одного раза). Ровно один номер появляется один раз. Как найти номер, который появляется только один раз?

e.g.: {1, 6, 3, 1, 1, 6, 6, 9, 3, 3, 3, 3}

Ответ - 9.

Я думал о том, чтобы иметь хеш-таблицу, а затем просто считать элемент, число которого равно 1. Это кажется тривиальным, и я не использую тот факт, что каждый другой элемент повторяется нечетное количество раз. Есть ли лучший подход.

Ответы [ 5 ]

38 голосов
/ 07 сентября 2011

Я полагаю, что вы все еще можете использовать основную идею XOR для умного решения этой проблемы.

Во-первых, давайте изменим проблему так, чтобы одно число появлялось один раз, а все остальные числа появлялись три раза.


Алгоритм:

Здесь A - массив длины n:

   int ones = 0;  
   int twos = 0;  
   int not_threes, x;  

   for (int i=0; i<n; ++i) {  
            x =  A[i];  
            twos |= ones & x;  
            ones ^= x;  
            not_threes = ~(ones & twos);  
            ones &= not_threes;  
            twos &= not_threes;  
   }  

И элемент, который встречается ровно один раз, сохраняется в ones. Это использует O(n) время и O(1) пространство.

Я полагаю, что могу распространить эту идею на общий случай проблемы, но, возможно, один из вас сможет сделать это быстрее, поэтому я оставлю это сейчас и отредактирую, когда и если смогу обобщить решение.


Пояснение:

Если бы проблема заключалась в следующем: «один элемент появляется один раз, а все остальные - четное число раз», тогда решение будет заключаться в XOR элементов. Причина в том, что x^x = 0, поэтому все парные элементы исчезнут, оставив только одинокий элемент. Если мы попробуем ту же тактику здесь, у нас останется XOR различных элементов, а это не то, что мы хотим.

Вместо этого приведенный выше алгоритм выполняет следующие действия:

  • ones - это XOR всех элементов, которые до сих пор появлялись ровно один раз
  • twos - это XOR всех элементов, которые появились ровно в два раза больше

Каждый раз, когда мы принимаем x в качестве следующего элемента в массиве, есть три случая:

  1. если это первый раз, когда x появился, он XORed в ones
  2. если это второй раз, когда появляется x, он извлекается из ones (путем повторного XORING) и XORed в twos
  3. если это третий раз, когда появляется x, он берется из ones и twos.

Следовательно, в итоге ones будет XOR только одного элемента, одинокого элемента, который не повторяется. Есть пять строк кода, на которые нам нужно обратить внимание, чтобы понять, почему это работает: пять после x = A[i].

Если это первый раз, когда появляется x, то ones&x=ones, поэтому twos остается без изменений. Линия ones ^= x; XOR x с ones как заявлено. Следовательно, x находится точно в одном из ones и twos, поэтому в последних трех строках ничего не происходит с ones или twos.

Если это второй раз, когда появляется x, то ones уже имеет x (по объяснению выше), поэтому теперь twos получает его со строкой twos |= ones & x;. Кроме того, поскольку ones имеет x, строка ones ^= x; удаляет x из ones (потому что x^x=0). Еще раз, последние три строки ничего не делают, поскольку точно одна из ones и twos теперь имеет x.

Если это третий раз, когда появляется x, то ones не имеет x, а twos. Итак, первая строка давайте twos оставим x, а вторая добавляет x к ones. Теперь, поскольку ones и twos имеют x, последние три строки удаляют x из обоих.


Обобщение:

Если некоторые числа появляются 5 раз, то этот алгоритм все еще работает. Это потому, что появляется 4-й раз x, но ни в ones, ни twos. Первые две строки затем добавляют x к ones, а не twos, а последние три строки ничего не делают. 5-й раз появляется x, он в ones, но не twos. Первая строка добавляет его к twos, вторая удаляет его из ones, а последние три строки ничего не делают.

Проблема в том, что появляется шестой раз x, он берется из ones и twos, поэтому он добавляется к ones на 7-м проходе. Я пытаюсь придумать умный способ предотвратить это, но пока я выхожу пустым.

21 голосов
/ 09 сентября 2011

Для указанной задачи наиболее вероятно, что наиболее эффективным ответом является O (n) пробел. С другой стороны, если мы сузим задачу до «Все числа появляются n раз, кроме одного, который появляется только один раз» или даже «Все числа появляются кратно n раз, кроме одного, который появляется только один раз», то это довольно просто. решение для любого n (очевидно, больше 1), которое занимает только O (1) пространство, которое состоит в том, чтобы разбить каждое число на биты, а затем подсчитать, сколько раз каждый бит включен и принять это значение по модулю n. Если результат равен 1, то он должен быть включен в ответ. Если это 0, то он должен быть выключен. (Любой другой ответ показывает, что параметры задачи не выполнялись). Если мы рассмотрим ситуацию, когда n равно 2, мы увидим, что использование XOR делает именно это (побитовое сложение по модулю 2). Мы просто обобщаем вещи, чтобы сделать побитовое сложение по модулю n для других значений n.

Это, кстати, то, что делает другой ответ для n = 3, на самом деле это просто сложный способ побитового сложения, где он хранит 2-битное число для каждого бита. Int, называемое "ones", содержит бит единицы, а int, называемое "twos", содержит бит двойки. Int not_threes используется для установки обоих битов в ноль, когда счет достигает 3, таким образом считая биты по модулю 3, а не как обычно (что будет по модулю 4, так как биты будут оборачиваться). Самый простой способ понять его код - это 2-битный аккумулятор с дополнительной частью, чтобы он работал по модулю 3.

Итак, для случая, когда все числа появляются кратные 3 раза, кроме одного уникального числа, мы можем написать следующий код для 32-битных целых чисел:

int findUnique(int A[], int size) {
  // First we set up a bit vector and initialize it to 0.
  int count[32];
  for (int j=0;j<32;j++) {
    count[j] = 0;
  }

  // Then we go through each number in the list.
  for (int i=0;i<size;i++) {
    int x = A[i];

    // And for each number we go through its bits one by one.
    for (int j=0;j<32;j++) {
      // We add the bit to the total.
      count[j] += x & 1;
      // And then we take it modulo 3.
      count[j] %= 3;
      x >>= 1;
    }
  }

  // Then we just have to reassemble the answer by putting together any
  // bits which didn't appear a multiple of 3 times.
  int answer = 0;
  for (int j=31;j>=0;j--) {
    answer <<= 1;
    if (count[j] == 1) {
      answer |= 1;
    }
  }

  return answer;
}

Этот код немного длиннее, чем другой ответ (и внешне выглядит более сложным из-за дополнительных циклов, но каждый из них имеет постоянное время), но, надеюсь, его легче понять. Очевидно, что мы могли бы уменьшить объем памяти, упаковывая биты более плотно, поскольку мы никогда не используем более двух из них для любого числа в счетчике. Но я не удосужился сделать это, так как это не влияет на асимптотическую сложность.

Если мы хотим изменить параметры задачи, чтобы числа повторялись 5 раз, мы просто изменили 3 с на 5 с. Или мы можем сделать то же самое для 7, 11, 137, 727 или любого другого числа (включая четные числа). Но вместо фактического числа мы можем использовать любой его коэффициент, поэтому для 9 мы можем просто оставить его равным 3, а для четных чисел мы можем просто использовать 2 (и, следовательно, просто использовать xor).

Однако для исходной задачи не существует общего решения на основе подсчета битов, где число может повторяться любое нечетное количество раз. Это связано с тем, что даже если мы точно посчитаем биты без использования по модулю, когда мы смотрим на конкретный бит, мы просто не можем знать, представляет ли 9 раз, когда он появляется, 3 + 3 + 3 или 1 + 3 + 5. включается в три разных числа, каждое из которых появилось три раза, тогда оно должно быть отключено в нашем ответе. Если он был включен в число, которое появилось один раз, число, которое появилось три раза, и число, которое появилось пять раз, то это должно быть включено в нашем ответе. Но с помощью только количества битов мы не можем знать это.

Вот почему другой ответ не обобщает, и умная идея для обработки особых случаев не будет реализована: любая схема, основанная на взгляде на вещи постепенно, чтобы выяснить, какие биты должны быть включены или выключены, делает не обобщать. Учитывая это, я не думаю, что любая схема, которая занимает пространство O (1), работает для общего случая. Возможно, что есть умные схемы, которые используют O (LG N) пространство и т. Д., Но я бы сомневался в этом. Я думаю, что пространственный подход O (n), вероятно, является лучшим, что может быть сделано в предложенной задаче. Я не могу доказать это, но на данный момент, это то, что говорит мне мой инстинкт, и я надеюсь, что я, по крайней мере, убедил вас, что небольшие изменения в методе «четного числа» не помогут.

2 голосов
/ 09 сентября 2011

Я знаю, что подтекст этого вопроса - найти эффективное или производительное решение, но я думаю, что простейший, читаемый код очень важен, и в большинстве случаев его более чем достаточно.

как насчет этого:

var value = (new [] { 1, 6, 3, 1, 1, 6, 6, 9, 3, 3, 3, 3, })
    .ToLookup(x => x)
    .Where(xs => xs.Count() == 1)
    .First()
    .Key;

Старый добрый LINQ.: -)

0 голосов
/ 30 июля 2016

Java, корректность 100%, производительность 100%, оценка задачи 100%

// you can also use imports, for example:
// import java.util.*;

// you can write to stdout for debugging purposes, e.g.
// System.out.println("this is a debug message");
import java.util.HashMap;

class Solution {

     /*Simple solution 
          Will be using HashMap(for performance) as Array, 
          only Key set is needed.
          Initially tried with ArryList but there was performance issue with
          that so switch to HashMap.
          Iterate over the given array and if item is there in key set         
          remove it(coz you found your pair) otherwise add as new Key.
          After full Iteration only one key will be there in the Map that is 
          your solution.
          In Short: If pair is found, remove it from Map's Key set,
          Last Key will be your solution
    */
    public int solution(int[] A) {

        //Map but used as Array
        final HashMap<Integer, Boolean> mapHave = new HashMap<>();

        //Iterate over given Array
        for (int nIdx = 0; nIdx < A.length; nIdx++) {

            //Current Item
            Integer nVal = A[nIdx];

            //Try to remove the current item, if item does not exists will
            //return null and if condition will be true
            if (mapHave.remove(nVal) == null) {
                //current item not found, add it
                mapHave.put(nVal, Boolean.TRUE);
            }
        }

        //There will be only one key remaining in the Map, that is
        //your solution
        return mapHave.keySet().iterator().next();
    }
}
0 голосов
/ 22 июня 2016

Результаты теста 100% с помощью c #

using System;
using System.Collections.Generic;
// you can also use other imports, for example:
// using System.Collections.Generic;

// you can write to stdout for debugging purposes, e.g.
// Console.WriteLine("this is a debug message");

class Solution {
    public int solution(int[] A) {

        Dictionary<int, int> dic =new Dictionary<int, int>();

        foreach(int i in A)
        {
            if (dic.ContainsKey(i))
            {
                dic[i]=dic[i]+1;                
            }
            else
            {
                dic.Add(i, 1);                
            }
        }

        foreach(var d in dic)
        {
            if (d.Value%2==1)
            {
                 return d.Key;
            }
        }

        return -1;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...