Поиск пробелов в последовательности чисел

У меня есть std::вектор, содержащий несколько чисел, которые не находятся в каком-либо определенном порядке, и могут или не могут иметь пробелы между числами - например, у меня может быть { 1,2,3, 6 } или { 2,8,4,6 } или { 1, 9, 5, 2 }, etc.

Я хотел бы простой способ посмотреть на этот вектор и сказать: "Дайте мне наименьшее число >= 1, которое делает не появится в векторе'. Итак,

для трех приведенных выше примеров ответы будут 4, 1 и 3 соответственно.

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

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

9 ответов


проверенный ответ использует

int find_gap(std::vector<int> vec) {
    std::sort(vec.begin(), vec.end());
    int next = 1;
    for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
        if (*it != next) return next;
       ++next;
    }
    return next;
}

find_gap(1,2,4,5) = 3
find_gap(2) = 1
find_gap(1,2,3) = 4

Я не передаю ссылку на вектор, так как a) он сказал, что время не имеет значения и b) поэтому я не изменяю порядок исходного вектора.


стандартный алгоритм, который вы ищете является std:: adjacent_find.

вот решение, которое также использует лямбда, чтобы сделать предикат чистым:

int first_gap( std::vector<int> vec )
{
  // Handle the special case of an empty vector.  Return 1.
  if( vec.empty() )
    return 1;

  // Sort the vector
  std::sort( vec.begin(), vec.end() );

  // Find the first adjacent pair that differ by more than 1.
  auto i = std::adjacent_find( vec.begin(), vec.end(), [](int l, int r){return l+1<r;} );

  // Handle the special case of no gaps.  Return the last value + 1.
  if ( i == vec.end() )
    --i;

  return 1 + *i;
}

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


вы можете выделить битовый вектор (той же длины, что и входной вектор), инициализировать его до нуля, а затем пометить все индексы (обратите внимание, что числа больше длины можно игнорировать). Затем верните первый немаркированный индекс (или длину, если все индексы отмечены, что происходит, только если все индексы происходят ровно один раз во входном векторе).

Это должно быть асимптотически быстрее, чем сортировка и поиск. Он будет использовать больше памяти, чем сортировка, если вам разрешено разрушать оригинал, но меньше памяти, чем сортировка, если вы должны сохранить оригинал.


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


Sort-n-поиск:

std::sort(vec.begin(), vec.end());
int lowest = 1;
for(size_t ii = 1; ii < vec.size(); ++ii)
{
    if (vec[ii - 1] + 1 < vec[ii])
    {
        lowest = (vec[ii - 1] + 1);
        break;
    }
}

/* 1, 2, ..., N case */
if (lowest == vec[0]) lowest = (*vec.back()) + 1;

итераторы могут использоваться с таким же ясным намерением, как продемонстрированы в @joe_mucchiello's (Эд: лучше) ответа.


хорошо, вот мои 2 цента. Предположим, у вас есть вектор длины N.

  1. Если N
  2. во-первых, используйте min_element, чтобы получить наименьший элемент, запомните его как emin
  3. вызовите nth_element, чтобы получить элемент в N / 2, назовите его ehalf
  4. Если ehalf != emin+N/2 слева есть пробел, примените этот метод рекурсивно, вызвав nth_element по всему массиву, но попросив элемент N / 4. В противном случае рекурсия справа запрос элемента 3*N / 4.

Это должно быть немного лучше, чем сортировка полностью вперед.


вы могли бы пойти с чем-то подобным....

struct InSequence
{
    int _current;   bool insequence;
    InSequence() : _current(1), insequence(true){}
    bool operator()(int x) {         
        insequence = insequence ? (x == _current) : false;  
        _current++; 
        return insequence;
    }
};

int first_not_in_sequence(std::vector<int>& v)
{
    std::sort(v.begin(), v.end());
    return 1+std::count_if(v.begin(), v.end(),InSequence());
}

возможная реализация ответа

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

решение, представленное здесь, является как можно более общим:

  • не предполагается тип контейнера или диапазона, за исключением того, что их итераторы должны соответствовать требованиям ValueSwappable и RandomAccessIterator (из-за частичной сортировки с nth_element)
  • можно использовать любой тип номера-необходимые черты описаны ниже

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

последний, но не менее важный рекурсивный подход может адаптироваться для отсортированных диапазонов! Если вы кодируете в параметре значения шаблона, уже ли диапазон отсортирован, вы можете просто пропустить частичную сортировку плюс сделать определение минимальных / максимальных элементов no-op.

#include <type_traits>
#include <iterator>
#include <tuple>
#include <utility>
#include <algorithm>
#include <cstddef>

// number type must be:
// * arithmetic
//   * subtractable (a - b)
//   * divisible by 2 (a / 2)
// * incrementable (++a)
// * less-than-comparable (a < b)
// * default-constructible (A{})
// * copy-constructible
// * value-constructible (A(n))
// * unsigned or number range must only contain values >0

/** Find lowest gap value in a range */
template<typename Range>
typename std::remove_reference_t<Range>::value_type
lowest_gap_value_unsorted(Range&& r)
{
    static_assert(!std::is_lvalue_reference_v<Range> && !std::is_const_v<Range>, "lowest_gap_value_unsorted requires a modifiable copy of the passed range");

    return lowest_gap_value_unsorted(std::begin(r), std::end(r), std::size(r));
}

/** Find lowest gap value in a range with specified size */
template<typename Range>
typename std::remove_reference_t<Range>::value_type
lowest_gap_value_unsorted(Range&& r, std::size_t N)
{
    static_assert(!std::is_lvalue_reference_v<Range> && !std::is_const_v<Range>, "lowest_gap_value_unsorted requires a modifiable copy of the passed range");

    return lowest_gap_value_unsorted(std::begin(r), std::end(r), N);
}

/** Find lowest gap value in an iterator range */
template<typename Iterator>
typename std::iterator_traits<Iterator>::value_type
lowest_gap_value_unsorted(Iterator first, Iterator last)
{
    return lowest_gap_value_unsorted(first, last, std::distance(first, last));
}

/** Find lowest gap value in an iterator range with specified size */

template<typename Iterator>
typename std::iterator_traits<Iterator>::value_type
lowest_gap_value(Iterator first, Iterator last, std::size_t N)
{
    typedef typename std::iterator_traits<Iterator>::value_type Number;

    if (bool empty = last == first)
        return increment(Number{});

    Iterator minElem, maxElem;
    std::tie(minElem, maxElem) = std::minmax_element(first, last);

    if (bool contains0 = !(Number{} < *minElem))
        throw std::logic_error("Number range must not contain 0");

    if (bool missing1st = increment(Number{}) < *minElem)
        return increment(Number{});

    if (bool containsNoGap = !(Number(N) < increment(*maxElem - *minElem)))
        return increment(*maxElem);

    return lowest_gap_value_unsorted_recursive(first, last, N, *minElem);
}

template<typename Iterator>
typename std::iterator_traits<Iterator>::value_type
lowest_gap_value_unsorted_recursive(Iterator first, Iterator last, std::size_t N, typename std::iterator_traits<Iterator>::value_type minValue)
{
    typedef typename std::iterator_traits<Iterator>::value_type Number;

    if (N == 1)
        return ++minValue;
    if (N == 2)
    {
        // determine greater of the 2 remaining elements
        Number maxValue = !(minValue < *first) ? *std::next(first) : *first;
        if (bool gap = ++minValue < maxValue)
            return minValue;
        else
            return ++maxValue;
    }

    Iterator medianElem = std::next(first, N / 2);
    // sort partially
    std::nth_element(first, medianElem, last);

    if (bool gapInLowerHalf = (Number(N) / 2 < *medianElem - minValue))
        return lowest_gap_value_unsorted_recursive(first, medianElem, N / 2, minValue);
    else
        return lowest_gap_value_unsorted_recursive(medianElem, last, N / 2 + N % 2, *medianElem);
};

template<typename T>
T increment(T v)
{
    return ++v;
}