Найти, есть ли элемент, повторяющийся n / k раз - PullRequest
10 голосов
/ 09 июня 2010

У вас есть размер массива n и константа k (что угодно)

Можно предположить, что массив имеет тип int (хотя он может быть любого типа))

Опишите алгоритм, который находит, существует ли элемент (ы), который повторяется, по крайней мере, n/k раз ... если есть возвращаемый.Делайте это за линейное время (O(n))

Подвох: используйте этот алгоритм (или даже псевдокод), используя постоянную память и выполняя над массивом только дважды

Ответы [ 6 ]

11 голосов
/ 09 июня 2010

Я не уверен на 100%, но звучит так, будто вы хотите решить проблему Бритни Спирс - найти элемент, составляющий определенную долю выборки, используя постоянную память.

Вот формулировка проблемы на английском языке с эскизом решения:

& hellip; из статьи 2002 года Эрика Д. Демейн из МТИ и Алехандро Лопес-Ортис и Дж. Ян Мунро из Университет Ватерлоо в Канаде. Демейн и его коллеги расширил алгоритм, чтобы покрыть более общая проблема: данный поток длины n, определить набор размером m который включает в себя все элементы происходит с большей частотой чем н / (м +1). (В случае m = 1, это сводит к проблеме большинства.) Расширенный алгоритм требует m регистры для элементов-кандидатов а также счетчики м. Основа Схема работы аналогична это алгоритм большинства. Когда элемент потока соответствует одному из кандидатов, соответствующий счетчик увеличивается; когда нет матча любому кандидату, все счетчики уменьшены; если счетчик равен 0, связанный кандидат заменен новым элементом из потока.

7 голосов
/ 09 июля 2014
  1. Создайте временный массив размера (k-1) для хранения элементов и их количества (выходные элементы будут среди этих элементов k-1).

  2. Пройдите через входной массив и обновите temp [] (добавьте / удалите элемент или увеличьте / уменьшите счетчик) для каждого пройденного элемента. Массив temp [] хранит потенциальных (k-1) кандидатов на каждом шаге. Этот шаг занимает O (NK) времени.

  3. Итерация по последним (k-1) потенциальным кандидатам (хранится в temp []). или каждый элемент, проверьте, действительно ли он имеет больше n / k. Этот шаг занимает O (NK) времени.

Основным шагом является шаг 2, как сохранить (k-1) потенциальных кандидатов в каждой точке? Шаги, использованные в шаге 2, похожи на известную игру: тетрис. Мы рассматриваем каждое число как кусок в тетрисе, который падает в нашем временном массиве temp []. Наша задача - постараться сохранить одинаковое число в одном и том же столбце (счетчик во временном массиве увеличивается).

Consider k = 4, n = 9 
Given array: 3 1 2 2 2 1 4 3 3 

i = 0
         3 _ _
temp[] has one element, 3 with count 1

i = 1
         3 1 _
temp[] has two elements, 3 and 1 with 
counts 1 and 1 respectively

i = 2
         3 1 2
temp[] has three elements, 3, 1 and 2 with
counts as 1, 1 and 1 respectively.

i = 3
         - - 2 
         3 1 2
temp[] has three elements, 3, 1 and 2 with
counts as 1, 1 and 2 respectively.

i = 4
         - - 2 
         - - 2 
         3 1 2
temp[] has three elements, 3, 1 and 2 with
counts as 1, 1 and 3 respectively.

i = 5
         - - 2 
         - 1 2 
         3 1 2
temp[] has three elements, 3, 1 and 2 with
counts as 1, 2 and 3 respectively. 
Now the question arises, what to do when temp[] is full and we see a new element – we remove the bottom row from stacks of elements, i.e., we decrease count of every element by 1 in temp[]. We ignore the current element.

i = 6
         - - 2 
         - 1 2 
temp[] has two elements, 1 and 2 with
counts as 1 and 2 respectively.

i = 7
           - 2 
         3 1 2 
temp[] has three elements, 3, 1 and 2 with
counts as 1, 1 and 2 respectively.

i = 8          
         3 - 2
         3 1 2 
temp[] has three elements, 3, 1 and 2 with
counts as 2, 1 and 2 respectively.

Наконец, у нас есть не более k-1 чисел в temp []. Элементы в temp являются {3, 1, 2}. Обратите внимание, что счетчики в temp [] теперь бесполезны, они нужны были только на шаге 2. Теперь нам нужно проверить, больше или нет фактическое количество элементов в temp [] больше n / k (9/4). Элементы 3 и 2 имеют число больше 9/4. Итак, мы печатаем 3 и 2.

Обратите внимание, что алгоритм не пропускает ни одного элемента вывода. Там может быть две возможности, много вхождений вместе или распределены по массиву. Если вхождения встречаются вместе, то количество будет высоким и не станет 0. Если вхождения распространятся, то элемент снова появится в temp []. Ниже приведена реализация вышеуказанного алгоритма на C ++.

// A C++ program to print elements with count more than n/k
#include<iostream>
using namespace std;

// A structure to store an element and its current count
struct eleCount
{
    int e;  // Element
    int c;  // Count
};

// Prints elements with more than n/k occurrences in arr[] of
// size n. If there are no such elements, then it prints nothing.
void moreThanNdK(int arr[], int n, int k)
{
    // k must be greater than 1 to get some output
    if (k < 2)
       return;

    /* Step 1: Create a temporary array (contains element
       and count) of size k-1. Initialize count of all
       elements as 0 */
    struct eleCount temp[k-1];
    for (int i=0; i<k-1; i++)
        temp[i].c = 0;

    /* Step 2: Process all elements of input array */
    for (int i = 0; i < n; i++)
    {
        int j;

        /* If arr[i] is already present in
           the element count array, then increment its count */
        for (j=0; j<k-1; j++)
        {
            if (temp[j].e == arr[i])
            {
                 temp[j].c += 1;
                 break;
            }
        }

        /* If arr[i] is not present in temp[] */
        if (j == k-1)
        {
            int l;

            /* If there is position available in temp[], then place 
              arr[i] in the first available position and set count as 1*/
            for (l=0; l<k-1; l++)
            {
                if (temp[l].c == 0)
                {
                    temp[l].e = arr[i];
                    temp[l].c = 1;
                    break;
                }
            }

            /* If all the position in the temp[] are filled, then 
               decrease count of every element by 1 */
            if (l == k-1)
                for (l=0; l<k; l++)
                    temp[l].c -= 1;
        }
    }

    /*Step 3: Check actual counts of potential candidates in temp[]*/
    for (int i=0; i<k-1; i++)
    {
        // Calculate actual count of elements 
        int ac = 0;  // actual count
        for (int j=0; j<n; j++)
            if (arr[j] == temp[i].e)
                ac++;

        // If actual count is more than n/k, then print it
        if (ac > n/k)
           cout << "Number:" << temp[i].e
                << " Count:" << ac << endl;
    }
}

/* Driver program to test above function */
int main()
{
    cout << "First Test\n";
    int arr1[] = {4, 5, 6, 7, 8, 4, 4};
    int size = sizeof(arr1)/sizeof(arr1[0]);
    int k = 3;
    moreThanNdK(arr1, size, k);

    cout << "\nSecond Test\n";
    int arr2[] = {4, 2, 2, 7};
    size = sizeof(arr2)/sizeof(arr2[0]);
    k = 3;
    moreThanNdK(arr2, size, k);

    cout << "\nThird Test\n";
    int arr3[] = {2, 7, 2};
    size = sizeof(arr3)/sizeof(arr3[0]);
    k = 2;
    moreThanNdK(arr3, size, k);

    cout << "\nFourth Test\n";
    int arr4[] = {2, 3, 3, 2};
    size = sizeof(arr4)/sizeof(arr4[0]);
    k = 3;
    moreThanNdK(arr4, size, k);

    return 0;
}
3 голосов
/ 10 декабря 2012

Существует два общих (теоретических) подхода к этой проблеме в O (n)

I) Первая идея самая простая

Шаг 1) Хотя существует более k различных элементов, выберите k различных элементов и сотрите их все.

Шаг 2) Проверить все k различных оставшихся элементов на его частоту

Доказательство правильности: Обратите внимание, что шаг будет выполнен не более n / k - 1 раз. Предположим, что есть элемент, который повторяется по крайней мере n / k раз. В худшем случае его можно было бы выбрать во всех n / k-1 итерациях, и он все равно будет в конечном массиве после него, после тестирования он будет найден.

Реализация: Шаг 1 может быть реализован с сохранением ассоциативного массива (сопоставляет ключ со значением) размера k-1 (константа), вы перемещаетесь по массиву слева направо, если вы найдете элемент, который уже находится на карте, увеличьте его Если счетчик равен 1, если элемент отсутствует на карте и карта еще не заполнена (менее k-1 элементов), добавьте этот новый элемент с начальным счетом 1, если карта заполнена, удалите 1 из счетчика каждого элемент, если какой-либо элемент достигает 0, удалите его с карты. В конце элементы на этой карте будут эквивалентны остальным элементам, которые нужно протестировать. Если на последней итерации ваша карта становится пустой, вам необходимо проверить все элементы перед удалением, чтобы охватить случай, когда частота равна n / k.

Сложность. Учитывая наихудший подход к этой карте, O (n * k) = O (n), так как k является постоянным.

Шаг 2 может быть реализован путем подсчета частоты всех (максимальных) k-1 элементов Сложность: O (k * n) = O (n)

Общая сложность: O (n) + O (n) = O (n). (есть небольшая деталь, которая отличалась от реализации, разница в 1 элемент, это происходит потому, что мы хотим также охватить случай частоты ровно n / k повторений в псевдокоде, если нет, мы могли бы допустить еще одну итерацию с точно k различными элементами, не обязательно больше чем k)

II) Второй алгоритм использует алгоритм выбора по линейному времени http://en.wikipedia.org/wiki/Selection_algorithm и алгоритм разделения, который также работает по линейному времени. Используя их, вы разбиваете свой массив на k-1 сегменты с тем инвариантом, что любой элемент в i-ом блоке меньше или равен любому элементу в j-ом сегменте для j> i в O (n). Но обратите внимание, что элементы не сортируются внутри каждого контейнера.

Теперь вы используете тот факт, что в каждом сегменте есть n / (k-1) элементов, и вы ищете элемент, который повторяется, по крайней мере, (n / k) и (n / k)> n / (2 * (к-1)). Для этого достаточно использовать теорему о мажоритарности, которая гласит, что если элемент является большинством (чаще, чем количество элементов, деленное на 2), то это также медиана массива. Вы можете получить медиану снова, используя алгоритм выбора.

Таким образом, вы просто тестируете все медианы и все оси для разделов, которые вам нужно проверить, потому что они могут разделить равные значения в два разных сегмента, есть значения k-1 + k, сложность O ((2 * k -1) * n)) = O (n).

0 голосов
/ 26 октября 2014

Это моя реализация алгоритма Jerky, описанного выше:

#include <map>
#include <vector>
#include <iostream>
#include <algorithm>

std::vector<int> repeatingElements(const std::vector<int>& a, int k)
{
    if (a.empty())
        return std::vector<int>();

    std::map<int, int> candidateMap; //value, count;

    for (int i = 0; i < a.size(); i++)
    {
        if (candidateMap.find(a[i]) != candidateMap.end())
        {
            candidateMap[a[i]]++;
        }
        else
        {
            if (candidateMap.size() < k-1)
            {
                candidateMap[a[i]] = 1;
            }
            else
            {
                for (std::map<int, int>::iterator iter = candidateMap.begin();
                     iter != candidateMap.end();)
                {
                    (iter->second)--;

                    if (iter->second == 0)
                    {
                        iter = candidateMap.erase(iter);
                    }
                    else
                    {
                        iter++;
                    }
                }   
            }
        }
    }

    std::vector<int> ret;

    for (std::map<int, int>::iterator iter = candidateMap.begin();
         iter != candidateMap.end(); iter++)
    {
        int candidate = iter->first;

        if (std::count(a.begin(), a.end(), candidate) > (a.size() / k))
        {
            ret.push_back(candidate);
        }
    }

    return ret;
}

int main()
{
    std::vector<int> a = { 1, 1, 4, 2, 2, 3, 3 };   
    int k = 4;

    std::vector<int> repeating_elements = repeatingElements(a, k);

    for (int elem : repeating_elements)
    {
        std::cout << "Repeating more than n/" << k << " : " << elem << std::endl;
    }

    return 0;
}

И вывод:

Повторение более n / 4: 1

Повторение более n / 4: 2

Повторение более n / 4: 3

0 голосов
/ 09 июня 2010

Я не знаю, ограничены ли вы какими дополнительными структурами данных вы можете использовать.

А как насчет создания хэш-карты с отображением 'elements' <-> count? Вставка O (log N). Поиск O (1). Для каждого элемента ищите в хеш-таблице, вставьте, если не существует, с счетчиком 1. Если существует, проверьте, если счет <(n / k). Это останется O (n). </p>

EDIT:

Я забыл постоянное ограничение памяти. Это предварительное распределение записей хеш-карты с разрешенными N элементами?

0 голосов
/ 09 июня 2010

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

...