Как определить, является ли массив перестановкой в ​​O (n)? - PullRequest
39 голосов
/ 20 мая 2010

Ввод: A только для чтения массив из N элементов, содержащих целочисленные значения от 1 до N (некоторые целочисленные значения могут появляться более одного раза!).И зона памяти размером фиксированный (10, 100, 1000 и т. Д. - не в зависимости от N).

Как сказать в O (n) если массив представляет собой перестановку?

- Чего я достиг к настоящему моменту (ответ доказал, что это не хорошо): -

  1. Я использую ограниченную область памяти для хранения суммы и произведения массива.
  2. Я сравниваю сумму с N * (N + 1) /2 и произведение с N!

Я знаю, что если условие (2) истинно, я может иметь перестановку.Мне интересно, есть ли способ доказать, что условие (2) достаточно, чтобы сказать, если у меня есть перестановка.Пока что я не понял этого ...

Ответы [ 16 ]

16 голосов
/ 20 мая 2010

Я немного скептически отношусь к тому, что есть решение. Ваша проблема, кажется, очень близка к той, которая была поставлена ​​несколько лет назад в математической литературе, с приведенным здесь кратким описанием («Проблема обнаружения дубликатов», С. Камаль Абдали, 2003) , в котором используется обнаружение цикла - - идея заключается в следующем:

Если есть дубликат, существует число j между 1 и N, так что следующее приведет к бесконечному циклу:

x := j;
do
{
   x := a[x];
}
while (x != j);

, поскольку перестановка состоит из одного или нескольких подмножеств S различных элементов s 0 , s 1 , ... s k-1 , где s j = a [s j-1 ] для всех j от 1 до k-1, а s 0 = a [s k-1 ], поэтому все элементы участвуют в циклах - один из дубликатов не будет частью такого подмножества.

например. если массив = [2, 1, 4, 6, 8 , 7, 9, 3, 8]

тогда элемент, выделенный жирным шрифтом в позиции 5, является дубликатом, поскольку все остальные элементы образуют циклы: {2 -> 1, 4 -> 6 -> 7 -> 9 -> 8 -> 3}. Тогда как массивы [2, 1, 4, 6, 5, 7, 9, 3, 8] и [2, 1, 4, 6, 3, 7, 9, 5, 8] являются действительными перестановками (с циклами {2 -> 1, 4 -> 6 -> 7 -> 9 -> 8 -> 3, 5} и {2 -> 1, 4 -> 6 -> 7 -> 9 -> 8 -> 5 -> 3} соответственно).

Абдали пытается найти дубликаты. В основном, следующий алгоритм (использующий алгоритм поиска цикла Флойда ) работает, если вы сталкиваетесь с одним из дубликатов:

function is_duplicate(a, N, j)
{
     /* assume we've already scanned the array to make sure all elements
        are integers between 1 and N */
     x1 := j;
     x2 := j;
     do
     {             
         x1 := a[x1];
         x2 := a[x2];
         x2 := a[x2];
     } while (x1 != x2);

     /* stops when it finds a cycle; x2 has gone around it twice, 
        x1 has gone around it once.
        If j is part of that cycle, both will be equal to j. */
     return (x1 != j);
}

Сложность в том, что я не уверен, что ваша проблема в том виде, в котором она изложена, совпадает с той, которая указана в его статье, и я также не уверен, что описанный им метод работает в O (N) или использует фиксированное количество места. Потенциальным контрпримером является следующий массив:

[3, 4, 5, 6, 7, 8, 9, 10, ... N-10, N-9, N-8, N-7, N-2, N-5, N-5 , N-3, N-5, N-1, N, 1, 2]

, который в основном представляет собой единичную перестановку, сдвинутую на 2, при этом элементы [N-6, N-4 и N-2] заменены на [N-2, N-5, N-5]. Это имеет правильную сумму (не правильный продукт, но я не принимаю продукт в качестве возможного метода обнаружения, поскольку требования к пространству для вычисления N! С произвольной арифметикой точности равны O (N), что нарушает дух «фиксированного пространства памяти» требование), и если вы попытаетесь найти циклы, вы получите циклы {3 -> 5 -> 7 -> 9 -> ... N-7 -> N-5 -> N-1} и {4 -> 6 -> 8 -> ... N-10 -> N-8 -> N-2 -> N -> 2}. Проблема состоит в том, что может быть до N циклов (тождественная перестановка имеет N циклов), каждый из которых занимает до O (N), чтобы найти дубликат, и вам нужно каким-то образом отслеживать, какие циклы были отслежены, а какие - нет. Я скептически отношусь к тому, что это можно сделать за фиксированное количество места. Но, возможно, это так.

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


edit: Я сделал спрашиваю о mathoverflow , там есть интересное обсуждение.

10 голосов
/ 20 мая 2010

Это невозможно сделать в O (1) пространстве, по крайней мере, с помощью алгоритма одиночного сканирования.

Proof

Предположим, вы обработали N / 2 из N элементов. Если предположить, что последовательность является перестановкой, то, учитывая состояние алгоритма, вы сможете определить набор из N / 2 оставшихся элементов. Если вы не можете выяснить оставшиеся элементы, то алгоритм можно обмануть, повторив некоторые из старых элементов.

Есть N, выберите N / 2 возможных оставшихся комплектов. Каждый из них должен быть представлен отдельным внутренним состоянием алгоритма, потому что иначе вы не могли бы выяснить остальные элементы. Однако для хранения X-состояний требуется логарифмическое пространство, поэтому для хранения N-состояний N / 2 требуется пространство BigTheta (log (N выберите N / 2)). Эти значения растут с ростом N, и поэтому внутреннее состояние алгоритма не может вписаться в пространство O (1).

Более формальное доказательство

Вы хотите создать программу P, которая с учетом конечных N / 2 элементов и внутреннего состояния алгоритма линейного времени-постоянной-пространства после обработки N / 2 элементов определяет, является ли вся последовательность перестановкой из 1..N. Для этой вторичной программы нет ни времени, ни места.

Предполагая, что P существует, мы можем создать программу Q, используя только внутреннее состояние алгоритма линейного времени-постоянной-пространства, которое определяет необходимые конечные N / 2 элементов последовательности (если это была перестановка). Q работает, передавая P каждый возможный конечный N / 2 элемент и возвращая множество, для которого P возвращает true.

Однако, поскольку Q имеет N, выбирает N / 2 возможных выходов, он должен иметь как минимум N, выбирает N / 2 возможных входов. Это означает, что внутреннее состояние исходного алгоритма должно хранить, по крайней мере, N выбирать N / 2 состояний, что требует BigTheta (журнал N выбирает N / 2), который больше, чем постоянный размер.

Следовательно, оригинальный алгоритм, который имеет границы времени и пространства, также не может работать правильно, если он имеет внутреннее состояние постоянного размера.

[Я думаю, что эта идея может быть обобщена, но мышление не доказывает.]

Последствия

BigTheta (log (N выберите N / 2)) равно BigTheta (N). Поэтому простое использование логического массива и тиковых значений по мере их появления (вероятно) является оптимальным в пространстве и оптимальным по времени, поскольку требует линейного времени.

5 голосов
/ 20 мая 2010

Я сомневаюсь, что вы сможете доказать это;)

  (1, 2, 4, 4, 4, 5, 7, 9, 9)

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

3 голосов
/ 20 мая 2010

Это не сработает из-за сложности, заданной как функция от N, а не от M, подразумевая, что N >> M

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

http://en.wikipedia.org/wiki/Bloom_filter

Для каждого элемента в массиве Запустите k хеш-функций Проверка на включение в фильтр Блума Если он есть, есть вероятность, что вы видели элемент раньше Если это не так, добавьте его

Когда вы закончите, вы можете также сравнить его с результатами массива 1..N по порядку, так как это будет стоить вам только N.

Теперь, если я не вставил достаточно предостережений. Это не 100% или даже близко, так как вы указали сложность в N, что подразумевает, что N >> M, так что по сути это не будет работать, как вы указали это.

Кстати, процент ложных срабатываний для отдельного предмета должен быть e = 2 ^ (- m / (n * sqrt (2)))

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

1 голос
/ 20 мая 2010

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

Есть еще много открытых вопросов, хотя, например, если sum(x_i)=N(N+1)/2 и product(x_i)=N! являются достаточными условиями, чтобы гарантировать перестановку, и какова вероятность того, что неперестановка генерирует ложный положительный результат (я надеюсь, ~ 1 / C для каждого C, который вы пытаетесь, но, возможно, нет).

1 голос
/ 20 мая 2010

Я не знаю, как это сделать в O (N), или даже если это можно сделать в O (N). Я знаю, что это можно сделать в O (N log N), если вы (используете соответствующий) сортировать и сравнивать.

При этом существует много методов O (N), которые можно сделать, чтобы показать, что один НЕ является перестановкой другого.

  1. Проверьте длину. Если неравенство, очевидно, не перестановка.
  2. Создание отпечатка пальца XOR. Если значение всех элементов XOR вместе не совпадает, то это не может быть перестановка. Однако совпадение будет неубедительным.
  3. Найти сумму всех элементов. Хотя результат может быть переполнен, это не должно беспокоить при сопоставлении с этим «отпечатком». Однако, если вы сделали контрольную сумму, которая включала умножение, переполнение было бы проблемой.

Надеюсь, это поможет.

0 голосов
/ 05 сентября 2015
int solution(int A[], int N) {
  int i,j,count=0, d=0, temp=0,max;
  for(i=0;i<N-1;i++) {
    for(j=0;j<N-i-1;j++) {
      if(A[j]>A[j+1]) {
        temp = A[j+1];
        A[j+1] = A[j];
        A[j] = temp;
      }
    }
  }
  max = A[N-1];
  for(i=N-1;i>=0;i--) {
    if(A[i]==max) {
      count++;
    }
    else {
      d++;
    }
    max = max-1;
  }
  if(d!=0) {
    return 0;
  }
  else {
    return 1;
  }
}
0 голосов
/ 18 апреля 2015

Решение Java ниже отвечает на вопрос частично. Сложность времени, я считаю, O (n). (Это убеждение основано на том факте, что решение не содержит вложенных циклов.) Насчет памяти - не уверен. Вопрос появляется первым по релевантным запросам в гугле, поэтому, возможно, он кому-нибудь пригодится.

public static boolean isPermutation(int[] array) {   
    boolean result = true;
    array = removeDuplicates(array);
    int startValue = 1;
    for (int i = 0; i < array.length; i++) {
        if (startValue + i  != array[i]){
            return false;
        }
    }
    return result;
}
public static int[] removeDuplicates(int[] input){
    Arrays.sort(input);
    List<Integer> result = new ArrayList<Integer>();
    int current = input[0];
    boolean found = false;

    for (int i = 0; i < input.length; i++) {
        if (current == input[i] && !found) {
            found = true;
        } else if (current != input[i]) {
            result.add(current);
            current = input[i];
            found = false;
        }
    }
    result.add(current);
    int[] array = new int[result.size()];
    for (int i = 0; i < array.length ; i ++){
        array[i] = result.get(i);
    }
    return array;
}
public static void main (String ... args){
    int[] input = new int[] { 4,2,3,4,1};
    System.out.println(isPermutation(input));
    //output true
    input = new int[] { 4,2,4,1};
    System.out.println(isPermutation(input));
    //output false
}
0 голосов
/ 27 декабря 2011

Проверьте следующее решение. Он использует O (1) дополнительный пробел. Он изменяет массив во время процесса проверки, но в конце возвращает его обратно в исходное состояние.

Идея такова:

  1. Проверьте, находится ли какой-либо из элементов вне диапазона [1, n] => O (n).
  2. Пройдите по номерам по порядку (теперь все они уверены, что находятся в диапазоне [1, n]), и для каждого числа x (например, 3):

    • перейдите в x-ю ячейку (например, [3]), если она отрицательная, то кто-то уже посещал ее до вас => Не перестановка. В противном случае (a [3] является положительным), умножьте его на -1. => O (n).
  3. Пройдите по массиву и уберите все отрицательные числа.

Таким образом, мы точно знаем, что все элементы находятся в диапазоне [1, n], и что нет дубликатов => Массив является перестановкой.

int is_permutation_linear(int a[], int n) {
    int i, is_permutation = 1;

    // Step 1.
    for (i = 0; i < n; ++i) {
        if (a[i] < 1 || a[i] > n) {
            return 0;
        }
    }

    // Step 2.
    for (i = 0; i < n; ++i) {
        if (a[abs(a[i]) - 1] < 0) {
            is_permutation = 0;
            break;
        }
        a[i] *= -1;
    }

    // Step 3.
    for (i = 0; i < n; ++i) {
        if (a[i] < 0) {
            a[i] *= -1;
        }
    }

    return is_permutation;
}

Вот полная программа, которая проверяет это:

/*
 * is_permutation_linear.c
 *
 *  Created on: Dec 27, 2011
 *      Author: Anis
 */

#include <stdio.h>

int abs(int x) {
    return x >= 0 ? x : -x;
}

int is_permutation_linear(int a[], int n) {
    int i, is_permutation = 1;

    for (i = 0; i < n; ++i) {
        if (a[i] < 1 || a[i] > n) {
            return 0;
        }
    }

    for (i = 0; i < n; ++i) {
        if (a[abs(a[i]) - 1] < 0) {
            is_permutation = 0;
            break;
        }
        a[abs(a[i]) - 1] *= -1;
    }

    for (i = 0; i < n; ++i) {
        if (a[i] < 0) {
            a[i] *= -1;
        }
    }

    return is_permutation;
}

void print_array(int a[], int n) {
    int i;
    for (i = 0; i < n; i++) {
        printf("%2d ", a[i]);
    }
}

int main() {
    int arrays[9][8] = { { 1, 2, 3, 4, 5, 6, 7, 8 },
                         { 8, 6, 7, 2, 5, 4, 1, 3 },
                         { 0, 1, 2, 3, 4, 5, 6, 7 },
                         { 1, 1, 2, 3, 4, 5, 6, 7 },
                         { 8, 7, 6, 5, 4, 3, 2, 1 },
                         { 3, 5, 1, 6, 8, 4, 7, 2 },
                         { 8, 3, 2, 1, 4, 5, 6, 7 },
                         { 1, 1, 1, 1, 1, 1, 1, 1 },
                         { 1, 8, 4, 2, 1, 3, 5, 6 } };
    int i;

    for (i = 0; i < 9; i++) {
        printf("array: ");
        print_array(arrays[i], 8);
        printf("is %spermutation.\n",
               is_permutation_linear(arrays[i], 8) ? "" : "not ");
        printf("after: ");
        print_array(arrays[i], 8);
        printf("\n\n");

    }

    return 0;
}

И его вывод:

array:  1  2  3  4  5  6  7  8 is permutation.
after:  1  2  3  4  5  6  7  8 

array:  8  6  7  2  5  4  1  3 is permutation.
after:  8  6  7  2  5  4  1  3 

array:  0  1  2  3  4  5  6  7 is not permutation.
after:  0  1  2  3  4  5  6  7 

array:  1  1  2  3  4  5  6  7 is not permutation.
after:  1  1  2  3  4  5  6  7 

array:  8  7  6  5  4  3  2  1 is permutation.
after:  8  7  6  5  4  3  2  1 

array:  3  5  1  6  8  4  7  2 is permutation.
after:  3  5  1  6  8  4  7  2 

array:  8  3  2  1  4  5  6  7 is permutation.
after:  8  3  2  1  4  5  6  7 

array:  1  1  1  1  1  1  1  1 is not permutation.
after:  1  1  1  1  1  1  1  1 

array:  1  8  4  2  1  3  5  6 is not permutation.
after:  1  8  4  2  1  3  5  6 
0 голосов
/ 14 августа 2011

Вот доказательство это невозможно сделать:

Предположим, по какой-то причине вы не обнаружили дубликатов во всех ячейках, кроме последней. Тогда проблема сводится к проверке, содержит ли последняя ячейка дубликат.

Если у вас до сих пор есть нет структурированного представления проблемного состояния, то вы сводитесь к выполнению линейного поиска по всему предыдущему входу для КАЖДОЙ ячейки. Легко видеть, как это оставляет вас с алгоритмом квадратичного времени.

Теперь, предположим, через какую-то умную структуру данных вы знаете, какое число вы ожидаете увидеть последним. Тогда, конечно, это знание занимает как минимум достаточно бит, чтобы сохранить искомое число - возможно, одну ячейку памяти? Но есть второе-последнее число и второе-последнее подзадача: тогда вы должны аналогичным образом представить набор из двух возможных чисел, которые еще предстоит увидеть. Это, безусловно, требует больше памяти, чем кодирование только для одного оставшегося числа. По мере развития аналогичных аргументов размер состояния должен увеличиваться вместе с размером проблемы, если только вы не готовы принять наихудший случай квадратичного времени.

Это компромисс между временем и пространством. Вы можете иметь квадратное время и постоянное пространство, или линейное время и линейное пространство. Вы не можете иметь линейное время и постоянное пространство.

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