подсчитать количество различных абсолютных значений среди элементов массива - PullRequest
8 голосов
/ 21 августа 2011

Мне задали вопрос на собеседование, чтобы найти количество различных абсолютных значений среди элементов массива. Я придумал следующее решение (на C ++), но интервьюер не был доволен эффективностью кода во время выполнения.

  1. Буду признателен за указания относительно того, как я могу улучшить эффективность выполнения этого кода?
  2. Кроме того, как рассчитать эффективность кода ниже? Цикл for выполняется A.size() раз. Однако я не уверен насчет эффективности STL std::find (В худшем случае это может быть O(n), поэтому код O(n²)?

Код:

int countAbsoluteDistinct ( const std::vector<int> &A ) {
  using namespace std;
  list<int> x;

  vector<int>::const_iterator it;
  for(it = A.begin();it < A.end();it++)
    if(find(x.begin(),x.end(),abs(*it)) == x.end())
      x.push_back(abs(*it));
  return x.size();
}

Ответы [ 13 ]

17 голосов
/ 21 августа 2011

Предложить альтернативный код заданному коду.

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

#include <vector>
#include <algorithm>
#include <iterator>

#include <cstdlib>

using namespace std;

int count_distinct_abs(vector<int> v)
{
    transform(v.begin(), v.end(), v.begin(), abs); // O(n) where n = distance(v.end(), v.begin())
    sort(v.begin(), v.end()); // Average case O(n log n), worst case O(n^2) (usually implemented as quicksort.
    // To guarantee worst case O(n log n) replace with make_heap, then sort_heap.

    // Unique will take a sorted range, and move things around to get duplicated
    // items to the back and returns an iterator to the end of the unique section of the range
    auto unique_end = unique(v.begin(), v.end()); // Again n comparisons
    return distance(v.begin(), unique_end); // Constant time for random access iterators (like vector's)
}

Преимущество здесь в том, что мы выделяем / копируем только один раз, если решаем брать по значению, а все остальное делается на месте, при этом средняя сложность составляет O(n log n) при размере v.

4 голосов
/ 21 августа 2011

std::find() является линейным (O (n)).Для этого я бы использовал отсортированный ассоциативный контейнер, в частности std :: set .

#include <vector>
#include <set>
using namespace std;

int distict_abs(const vector<int>& v)
{
   std::set<int> distinct_container;

   for(auto curr_int = v.begin(), end = v.end(); // no need to call v.end() multiple times
       curr_int != end;
       ++curr_int)
   {
       // std::set only allows single entries
       // since that is what we want, we don't care that this fails 
       // if the second (or more) of the same value is attempted to 
       // be inserted.
       distinct_container.insert(abs(*curr_int));
   }

   return distinct_container.size();
}

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

3 голосов
/ 21 августа 2011

Да, это будет O (N 2 ) - вы закончите линейным поиском для каждого элемента.

Можно использовать пару достаточно очевидных альтернатив.std::set или std::unordered_set.Если у вас нет C ++ 0x, вы можете заменить std::unordered_set на tr1::unordered_set или boost::unordered_set.

Каждая вставка в std::set равна O (log N), так что ваша общая сложностьравно O (N log N).

При использовании unordered_set каждая вставка имеет постоянную (ожидаемую) сложность, что в целом дает линейную сложность.

2 голосов
/ 31 декабря 2015

Так как я не был доволен предыдущим ответом, вот мой сегодня.Ваш начальный вопрос не упоминает, насколько велик ваш вектор.Предположим, ваш std::vector<> очень большой и имеет очень мало дубликатов (почему бы и нет?).Это означает, что использование другого контейнера (например, std::set<>) в основном дублирует потребление памяти.Зачем вам это делать, поскольку ваша цель - просто считать не дубликат.

Мне нравится @Flame ответ, но я не очень доволен звонком на std::unique.Вы потратили много времени на тщательную сортировку своего вектора, а затем просто отбросили отсортированный массив, в то время как впоследствии вы могли бы повторно использовать его.

Я не смог найти ничего действительно элегантного в библиотеке STD, поэтому вот мойпредложение (смесь std::transform + std::abs + std :: sort , но без последующего касания отсортированного массива).

// count the number of distinct absolute values among the elements of the sorted container
template<class ForwardIt>
typename std::iterator_traits<ForwardIt>::difference_type 
count_unique(ForwardIt first, ForwardIt last)
{
  if (first == last)
    return 0;

  typename std::iterator_traits<ForwardIt>::difference_type 
    count = 1;
  ForwardIt previous = first;
  while (++first != last) {
    if (!(*previous == *first) ) ++count;
    ++previous;
  }
  return count;
}

Бонусный пункт работает с прямым итератором:

#include <iostream>
#include <list>
int main()
{
  std::list<int> nums {1, 3, 3, 3, 5, 5, 7,8};
  std::cout << count_unique( std::begin(nums), std::end(nums) ) << std::endl;

  const int array[] = { 0,0,0,1,2,3,3,3,4,4,4,4};
  const int n = sizeof array / sizeof * array;
  std::cout << count_unique( array, array + n ) << std::endl;
  return 0;
}
2 голосов
/ 21 августа 2011

По сути, замените свой std :: list на std :: set.Это дает вам O (log (set.size ())) поиск + O (1) вставки, если вы все делаете правильно.Также для эффективности имеет смысл кэшировать результат abs (* it), хотя это будет иметь лишь минимальный (незначительный) эффект.Эффективность этого метода настолько хороша, насколько это возможно, без использования действительно хорошего хэша (std :: set использует bin-деревья) или дополнительной информации о значениях в векторе.

1 голос
/ 21 августа 2011

Два очка.

  1. std :: list очень плох для поиска. Каждый поиск O (n).

  2. Использовать std :: set. Вставка логарифмическая, удаляет дубликаты и сортируется. Вставьте каждое значение O (n log n), затем используйте set :: size, чтобы узнать, сколько значений.

EDIT:

Чтобы ответить на часть 2 вашего вопроса, стандарт C ++ предписывает наихудший случай для операций над контейнерами и алгоритмами.

Find : Так как вы используете бесплатную версию функции find, которая принимает итераторы, она не может предположить что-либо о переданной в последовательности последовательности, она не может предположить, что диапазон отсортирован, поэтому он должен проходить через каждый элемент пока не найдет совпадение, которое равно O (n).

Если вы используете set :: find , с другой стороны, этот элемент find может использовать структуру набора, и его производительность должна быть O (log N), где N - размер из набора.

0 голосов
/ 17 июня 2015

В вашем коде есть вложенные циклы.Если вы будете сканировать каждый элемент по всему массиву, это даст вам O (n ^ 2) временную сложность, которая неприемлема в большинстве сценариев.По этой причине появились алгоритмы Merge Sort и Quick sort , чтобы сэкономить циклы обработки и машинные усилия.Я предлагаю вам перейти по предлагаемым ссылкам и перепроектировать вашу программу.

0 голосов
/ 25 августа 2011

Еще один подход:

Эффективное пространство: используйте хэш-карту.O (logN) * ​​O (n) для вставки и просто сохранить количество успешно вставленных элементов.

Эффективное по времени: используйте для вставки хеш-таблицу O (n) и просто сохраняйте количество успешно вставленных элементов.

0 голосов
/ 25 августа 2011

Лучший способ - настроить алгоритм быстрой сортировки таким образом, чтобы при разбиении мы получали два равных элемента, а затем перезаписывали второй дубликат последним элементом в диапазоне, а затем уменьшали диапазон.Это гарантирует, что вы не будете обрабатывать дубликаты элементов дважды.Кроме того, после быстрой сортировки диапазон элемента соответствует ответу Сложность по-прежнему равна O (n * Lg-n), НО это должно сохранить как минимум два прохода по массиву.

Кроме того, экономия пропорциональна% от дубликатов.Представьте себе, если они закручивают оригинальный квест с «скажем, 90% элементов дублируются» ...

0 голосов
/ 25 августа 2011

Сортировка списка с сортировкой по стилю Radix для эффективности O (n). Сравните соседние значения.

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