Алгоритм определения наличия в массиве nn + m?

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

Напишите метод, который принимает массив int размера m и возвращает (True / False), если массив состоит из чисел n ... n + m-1, всех чисел в этом диапазоне и только чисел в этом диапазоне. Сортировка массива не гарантируется. (Например, {2,3,4} вернет истину. {1,3,1} вернет ложь, {1,2,4} вернет ложь.

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

Наряду с вашими решениями укажите, предполагают ли они, что массив содержит уникальные элементы. Также укажите, предполагает ли ваше решение, что последовательность начинается с 1. (Я немного изменил вопрос, чтобы разрешить случаи, когда он идет 2, 3, 4 ...)

edit: Сейчас я придерживаюсь мнения, что не существует линейного во времени и постоянного в пространстве алгоритма, который обрабатывает дубликаты. Кто-нибудь может это проверить?

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


person Community    schedule 07.10.2008    source источник
comment
Вы действительно имели в виду массив размером m (не n)? Похоже на ваш пример.   -  person Mark Ransom    schedule 07.10.2008
comment
Вот набор задач для претендентов: [1,1,4,4,5]. следует = ложь. суммирование думает, что все в порядке.   -  person Kent Fredric    schedule 07.10.2008
comment
Для данной проблемы вы можете указать, что это можно сделать в пространстве O (1), поскольку указан массив int. Я представил возможное решение в этом случае. Однако для неограниченного ввода я не верю, что пространство O (1) возможно. (Хотя я действительно думаю, что мы могли бы сделать лучше, чем O (n) пробел)   -  person hurst    schedule 07.10.2008
comment
Решение O(m) (single pass) and O(1) in space не имеет контрпримеров - алгоритм uniqueSet stackoverflow.com/questions/177118/   -  person jfs    schedule 09.10.2008
comment
Вы говорите, что {1,3,1} должен возвращать false, но m здесь 3, n = 1, все числа в массиве находятся в диапазоне 1..3, поэтому я утверждаю, что это должно возвращать true в соответствии с к описанию проблемы.   -  person paxos1977    schedule 10.10.2008
comment
@austirg: проблема заключается в том, что значение true возвращается только в том случае, если вектор содержит все числа в диапазоне. В примере {1, 3, 1} 2 отсутствует.   -  person b3.    schedule 10.10.2008
comment
Дж. Ф. Себастьян, как вы указали ниже, решение uniqueSet не является пространством O (1). Это O (m), потому что для этого требуется m дополнительных бит памяти.   -  person Derek Park    schedule 10.10.2008
comment
@Derek: Дополнительное хранилище требуется только в версии Ruby, где оно всегда работает, но в версии C не требует дополнительного хранилища, но может дать сбой в некоторых последовательностях, но я еще не видел пример счетчика.   -  person jfs    schedule 10.10.2008
comment
Я добавил контрпример для алгоритма uniqueSet C версии stackoverflow.com/questions/177118/   -  person jfs    schedule 10.10.2008
comment
Это легко, если вы предположите, что факториал доступен с желаемой характеристикой производительности. Если вам нужно линейное время, тогда вы золотой.   -  person Marcin    schedule 12.10.2008
comment
@Marcin: факториальный контрпример: [1, 2, 4, 4, 4, 5, 7, 9, 9]. Произведение (9! = 362880) и сумма (45) совпадают с [1, 2, 3, 4, 5, 6, 7, 8, 9].   -  person jfs    schedule 19.10.2008
comment
Я нашел время O (m), пространственное решение O (1) (для int m) stackoverflow.com/questions/177118/# 311497   -  person jfs    schedule 22.11.2008
comment
Из вашей постановки задачи неясно, является ли n входным параметром задачи.   -  person AnT    schedule 06.11.2009
comment
@AndreyT: нет, это не так, это просто способ описать последовательность   -  person Kyle Cronin    schedule 07.11.2009


Ответы (37)


В предположении, что числа меньше единицы недопустимы и отсутствуют дубликаты, для этого существует простая процедура суммирования - сумма чисел от 1 до m с приращениями 1 равна (m * (m + 1)) / 2. Затем вы можете суммировать массив и использовать этот идентификатор.

Вы можете узнать, есть ли обман по вышеуказанным гарантиям, плюс гарантия, что номер не превышает m или меньше n (что можно проверить в O(N))

Идея в псевдокоде:
0) Начать с N = 0
1) Возьмите N-й элемент в списке.
2) Если он не в нужном месте, если список был отсортировано, проверьте, где он должен быть.
3) Если место, где оно должно быть, уже имеет тот же номер, у вас есть обман - ВОЗВРАЩАЕТСЯ ИСТИНА
4) В противном случае поменяйте местами числа (чтобы поместить первое число в в нужном месте).
5) С номером, который вы только что поменяли местами, он в нужном месте?
6) Если нет, вернитесь к шагу 2.
7) В противном случае начните с шага 1 с N = N + 1. Если это будет за концом списка, у вас нет дубликатов.

И да, это работает в O(N), хотя может выглядеть O(N ^ 2)

Всем на заметку (материал собран из комментариев)

Это решение работает в предположении, что вы можете изменить массив, а затем использует сортировку Radix на месте (что обеспечивает скорость O(N)).

Были предложены и другие математические решения, но я не уверен, что какое-либо из них было доказано. Существует множество сумм, которые могут быть полезны, но большинство из них приводит к чрезмерному увеличению количества битов, необходимых для представления суммы, что нарушает постоянную гарантию дополнительного пространства. Я также не знаю, способен ли какой-либо из них дать отличное число для данного набора чисел. Я думаю, что может работать сумма квадратов, для вычисления которой используется известная формула (см. Wolfram's)

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

Итак, было упомянуто, что можно использовать сумму + сумма квадратов. Никто не знал, сработало это или нет, и я понял, что это становится проблемой только тогда, когда (x + y) = (n + m), например, факт 2 + 2 = 1 + 3. Квадраты также имеют эту проблему благодаря троек Пифагора (поэтому 3 ^ 2 + 4 ^ 2 + 25 ^ 2 == 5 ^ 2 + 7 ^ 2 + 24 ^ 2, и сумма квадратов не работает). Если мы воспользуемся последней теоремой Ферма, мы знаем, что этого не может произойти для n ^ 3. Но мы также не знаем, нет ли для этого x + y + z = n (если только мы этого не сделаем, а я этого не знаю). Так что нет гарантии, что это тоже не сломается - и если мы продолжим идти по этому пути, у нас быстро закончатся биты.

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


Я должен сказать, что найти контрпримеры иногда намного проще, чем что-то доказывать! Рассмотрим следующие последовательности, каждая из которых имеет сумму 28 и сумму квадратов 140:

[1, 2, 3, 4, 5, 6, 7]
[1, 1, 4, 5, 5, 6, 6] 
[2, 2, 3, 3, 4, 7, 7]

Таких примеров длиной 6 и меньше я найти не смог. Если вам нужен пример с правильными минимальными и максимальными значениями, попробуйте этот длиной 8:

[1, 3, 3, 4, 4, 5, 8, 8]

Более простой подход (изменение идеи Хаззена):

Целочисленный массив длины m содержит все числа от n до n + m-1 ровно один раз, если и только если

  • каждый элемент массива находится между n и n + m-1
  • нет дубликатов

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

Если вам разрешено изменять массив, вы можете проверить оба за один проход по списку с модифицированной версией идеи алгоритма Хаззена (нет необходимости производить какое-либо суммирование):

  • For all array indexes i from 0 to m-1 do
    1. If array[i] < n or array[i] >= n+m => RETURN FALSE ("value out of range found")
    2. Вычислить j = array [i] - n (отсчитываемая от 0 позиция array [i] в ​​отсортированном массиве со значениями от n до n + m-1)
    3. While j is not equal to i
      1. If list[i] is equal to list[j] => RETURN FALSE ("duplicate found")
      2. Поменять местами список [i] на список [j]
      3. Пересчитать j = array [i] - n
  • ВОЗВРАТ ИСТИНА

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

person Community    schedule 07.10.2008
comment
Я оставлю это читателю в качестве упражнения, если вы действительно не хотите знать. - Я действительно хочу знать - мне кажется, это сложная часть этой проблемы. - person Smashery; 07.10.2008
comment
Как и я. В какой-то момент я думал, что у меня есть решение, но оно не сработало. - person Kyle Cronin; 07.10.2008
comment
Вы можете решить похожую проблему поиска одного не дубликата в массиве дубликатов с помощью XOR ... возможно, это то, о чем думал Хаззен? - person Hugh Allen; 07.10.2008
comment
XOR не работает; рассмотрим 1..6, что равно 21, и XOR всех цифр равен 7. Но 6 + 6 + 5 + 1 + 1 + 2 также суммируется до 21 и также имеет XOR 7. - person hazzen; 07.10.2008
comment
Возможно, я неправильно понимаю ваш алгоритм, но (5,3,3,3,1), кажется, меняет местами 5 и 1 на шагах 1-6, а затем получает 6 для шага 7, вычисляя, что дубликатов нет. - person Randy; 07.10.2008
comment
Вы должны отслеживать, с чего вы начали в прошлый раз, и начинать сразу после этого. Итак, вы должны начать со второго числа. Сделаю описание более понятным. - person hazzen; 07.10.2008
comment
Если это так, то ваш алгоритм сортирует массив, если нет дубликатов, поэтому мне трудно поверить, что он может сделать это за время O (N). - person Randy; 07.10.2008
comment
Это сортировка с использованием идей, лежащих в основе сортировки по основанию, которая в этих условиях составляет O (N) времени и пространства, и у нас уже есть пространство O (N), если мы делаем это на месте. - person hazzen; 07.10.2008
comment
Да, проследив это на нескольких примерах, я думаю, что вы правы. В условиях ограничений, налагаемых проблемой, вы всегда будете знать правильное местоположение для каждого элемента, поэтому вам придется проверять каждый элемент ровно один раз, если нет дубликатов, и вы остановитесь раньше, если они есть. - person Randy; 07.10.2008
comment
Где-то определенно есть противоречие. Проблема говорит, что используйте пространство O (1), и вы хитро используете пространство O (n). Я имею в виду, что если нам разрешено использовать пространство O (n), есть гораздо более простые способы проверки дубликатов. - person andy; 07.10.2008
comment
Я согласен, мое решение работает только в том случае, если вы можете изменить массив, тогда я использую трюк с сортировкой по основанию, чтобы получить сортировку O (N). Если вы не можете изменить массив, возможно, нам не хватает некоторых математических приемов. - person hazzen; 07.10.2008
comment
А может быть, проблема неразрешима. Я не даю никаких гарантий по этому поводу. :) Ваше решение не использует никакого дополнительного хранилища, поэтому можно утверждать, что это пространство O (1), но это не O (1) в прямом смысле. Однако, если вы дадите такой ответ в интервью, интервьюера было бы глупо, если бы его не наняли. - person Kyle Cronin; 07.10.2008
comment
По вашему первому пункту о сумме массива. Измените уравнение на m * (m + 2 * n - 1) / 2, и это также работает для отрицательных чисел. - person nickf; 07.10.2008
comment
Я отправил ответ. Используйте XOR с четными и нечетными числами отдельно. XOR имеет свойство накопления, а также вам не нужно думать о переполнении или потере значимости при суммировании. - person popopome; 07.10.2008
comment
Суммирование не требуется, если мы можем проверить дубликаты. В этом случае будет достаточно n == min (массив), (n + m-1) == max (array). Другими словами, inplace-bucket-sort + min + max == решение. - person jfs; 08.10.2008
comment
Думаю, что есть проблема с сортировкой массива за один проход, не зная заранее n. Я опубликовал обходной путь в своем ответе. - person Rafał Dowgird; 09.10.2008
comment
Сортировка по основанию счисления на месте работает только в том случае, если НЕТ КЛЮЧА УЖЕ В ПРАВИЛЬНОМ МЕСТЕ. Из примера {2,3,4} может показаться, что вы НЕ МОЖЕТЕ использовать здесь сортировку по основанию счисления. - person paxos1977; 10.10.2008
comment
@austrig: Сортировка по месту работает. См. http://stackoverflow.com/questions/177118/algorithm-to-determine-if-array-contains-nnm#177662 - person jfs; 10.10.2008
comment
Это полный беспорядок. Есть несколько разных идей, которые следует разделить на отдельные ответы. Меня не волнует, будут ли они хорошими ответами, я голосую против, пока не приведу в порядок! - person Aaron McDaid; 17.01.2012

Работая с a[i] % a.length вместо a[i], вы сводите проблему к необходимости определить, что у вас есть числа от 0 до a.length - 1.

Мы принимаем это наблюдение как должное и пытаемся проверить, содержит ли массив [0, m).

Найдите первый узел, который не находится в правильном положении, например

0 1 2 3 7 5 6 8 4 ;     the original dataset (after the renaming we discussed)
        ^
        `---this is position 4 and the 7 shouldn't be here

Поменяйте местами это число на то, где оно должно быть. то есть поменять местами 7 на 8:

0 1 2 3 8 5 6 7 4 ; 
        |     `--------- 7 is in the right place.
        `--------------- this is now the 'current' position

Теперь повторяем это. Снова глядя на нашу текущую позицию, мы спрашиваем:

"Это правильный номер здесь?"

  • Если нет, то меняем его на правильное место.
  • Если он находится в нужном месте, мы двигаемся вправо и делаем это снова.

Снова следуя этому правилу, получаем:

0 1 2 3 4 5 6 7 8 ;     4 and 8 were just swapped

Это постепенно приведет к правильному построению списка слева направо, и каждое число будет перемещено не более одного раза, и, следовательно, это O (n).

Если есть дубли, мы заметим это, как только будет попытка поменять местами номер backwards в списке.

person Community    schedule 08.10.2008
comment
Другими словами, задача [n, n + m) эквивалентна [0, m). - person jfs; 08.10.2008
comment
Как определить, не проходя через массив ни разу? - person ; 19.08.2014

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

Более простой способ:

Шаг 1, выясните, есть ли дубликаты. Я не уверен, возможно ли это в пространстве O (1). В любом случае, верните false, если есть дубликаты.

Шаг 2: просматривайте список и отслеживайте самые низкие и самые высокие элементы.

Шаг 3. Равно ли (самый высокий - самый низкий) m? Если да, верните true.

person Community    schedule 07.10.2008
comment
Ваше решение напоминает мне: ТОГДА ПРОИСХОДИТ ЧУДО Я думаю, вам следует быть более точным здесь, на шаге 2 (шаг 1 в вашем примере), мультфильм. :) sciencecartoonsplus.com/gallery/math/math07.gif - person jfs; 08.10.2008
comment
Для первого шага либо требуется ›O (1) пробела, либо O (n) времени для вычисления, если для отслеживания суммы технически используется пробел› O (1), то также отслеживается самый высокий и самый низкий элемент ... - person Charles Ma; 10.10.2008
comment
Сумма занимает не более O (1) места. По мере увеличения размера задачи (в данном случае размера входного массива) сумма все еще занимает то же место и остается постоянной. - person Ron Warholic; 10.10.2008
comment
Сумма требует места. Сложение двух n-значных чисел дает (n + 1) -значное число. В противном случае мы могли бы выполнять вычисления с бесконечной точностью, используя числовое представление фиксированной ширины. en.wikipedia.org/wiki/ - person jfs; 10.10.2008
comment
Дж. Ф. Себастьян, два n-значных числа по-прежнему могут давать n-значное число. 2 + 3 = 5. Тем не менее, проблема переполнения - это совсем другой вопрос. Никто, задающий подобный вопрос в интервью, не будет беспокоиться о переполнении, по крайней мере, до тех пор, пока не будет освоен базовый алгоритм. - person Derek Park; 10.10.2008
comment
@Derek: 2 + 3 может переполниться. Это зависит от представления. Мы могли бы представить числа меньше 4 двумя битами, но мы не можем представить все числа ‹6 двумя битами, для этого требуется 3 бита. Следовательно, 2 + 3 = 1, если мы используем беззнаковые числа шириной 2 бита. Замените 2 бита на 32 бита, и вы получите общий случай. - person jfs; 10.10.2008
comment
Да, это зависит от представления. Однако вопрос не в этом. Переполнение - это проблема, которая затрагивает многие алгоритмы. Однако это не то, что обычно принимается во внимание при измерении сложности времени выполнения. - person Derek Park; 10.10.2008
comment
Что касается суммы, я заметил, что добавление двух чисел с x цифрами создает число не более чем с x + 1 цифрами, поэтому сумма увеличивается с log (numbers_to_be_summed), например, 8 чисел, 4 бита каждое, образуют 4 группы пар из 4-битных чисел каждая группа суммируется с 5-битным числом, 4 числа из 5-битных чисел создают две группы, которые суммируются с двумя 6-битными числами, которые суммируются с одним 7-битным числом, то есть (2 ^ 4 * 2 ^ 3), поэтому для практического использования его можно рассматривать как O (1) (добавление 2 ^ 64 чисел для каждого из 64 бит потребует 128 бит) - person Liran Orevi; 18.06.2009

Для любого однопроходного алгоритма требуется Omega (n) бит памяти.

Предположим противное, что существует однопроходный алгоритм, который использует o (n) бит. Поскольку он выполняет только один проход, он должен суммировать первые n / 2 значений в o (n) пространстве. Поскольку существует C (n, n / 2) = 2 ^ Theta (n) возможных наборов n / 2 значений, взятых из S = {1, ..., n}, существуют два различных набора A и B из n / 2 значения так, чтобы состояние памяти было одинаковым после обоих. Если A '= S \ A является «правильным» набором значений для дополнения A, то алгоритм не может правильно ответить на входные данные.

А А '- да

B A' - no

поскольку он не может отличить первый случай от второго.

Q.E.D.

person Community    schedule 14.10.2008

Вот рабочее решение за O (n)

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

function testArray($nums, $n, $m) {
    // check the sum. PHP offers this array_sum() method, but it's
    // trivial to write your own. O(n) here.
    if (array_sum($nums) != ($m * ($m + 2 * $n - 1) / 2)) {
        return false;    // checksum failed.
    }
    for ($i = 0; $i < $m; ++$i) {
        // check if the number is in the proper range
        if ($nums[$i] < $n || $nums[$i] >= $n + $m) {
            return false;  // value out of range.
        }

        while (($shouldBe = $nums[$i] - $n) != $i) {
            if ($nums[$shouldBe] == $nums[$i]) {
                return false;    // duplicate
            }
            $temp = $nums[$i];
            $nums[$i] = $nums[$shouldBe];
            $nums[$shouldBe] = $temp;
        }
    }
    return true;    // huzzah!
}

var_dump(testArray(array(1, 2, 3, 4, 5), 1, 5));  // true
var_dump(testArray(array(5, 4, 3, 2, 1), 1, 5));  // true
var_dump(testArray(array(6, 4, 3, 2, 0), 1, 5));  // false - out of range
var_dump(testArray(array(5, 5, 3, 2, 1), 1, 5));  // false - checksum fail
var_dump(testArray(array(5, 4, 3, 2, 5), 1, 5));  // false - dupe
var_dump(testArray(array(-2, -1, 0, 1, 2), -2, 5)); // true
person Community    schedule 07.10.2008
comment
Суммирование может привести к переполнению или использованию дополнительной памяти. Массив может быть доступен только для чтения. - person jfs; 08.10.2008
comment
На самом деле это O (n + m) во времени и O (m) в пространстве. - person jfs; 08.10.2008
comment
Нет такой вещи, как O (n + m). Упомянутое здесь n - это не то же самое n в решении. O (n) означает, что количество времени / ресурсов, необходимых для ее решения, линейно зависит от размера набора. en.wikipedia.org/wiki/Big_o_notation#Orders_of_common_functions - person nickf; 09.10.2008
comment
... хотя я признаю, что делать сумму с самого начала на самом деле необязательно. Я просто добавил его, потому что это был бы очень быстрый способ узнать, не работает ли массив. - person nickf; 09.10.2008
comment
Я использовал общую оценку сложности. Рассмотрим случай: n' records have keys in range [0, m). In your case n == m, therefore notation O(n+n) does not make sense indeed, but in general case n` может отличаться от m, тогда обозначение O (n + m) имеет смысл. - person jfs; 09.10.2008
comment
При использовании нотации Big O вы просто описываете, как увеличивается использование ресурсов (время / память) по мере увеличения размера вашего набора. Поскольку это решение увеличивается в линейном масштабе (то есть: время, необходимое для выполнения этой функции, равно t = k * n, где t - время, k - константа, а n - ... продолжение. - person nickf; 09.10.2008
comment
... а n - размер набора. Если вы установили размер набора 5, выполнение функции O (n) может занять 2 секунды. Если вы увеличите заданный размер вдвое до 10, это займет 4 секунды. Вы не будете вдаваться в подробности с Big-O, просто опишите отношения. Это O (n). - person nickf; 09.10.2008
comment
Я всегда думал, что O (n) определяется через limit[n->+inf]( O(n)/n ) <= C, где C - константа. - person jfs; 09.10.2008
comment
Пример: деление двух n-битных чисел занимает время O (n (m + 1)), где m - длина частного. en.wikipedia.org/wiki/Euclidean_algorithm#Running_time Это показывает, что мы можем использовать больше затем один параметр при использовании нотации Big O. - person jfs; 10.10.2008
comment
хорошо, хорошо в вашем первоначальном комментарии, что это O (n + m), что такое n и m? Единственная переменная, которая имеет какое-либо влияние на время выполнения этого уравнения, - это размер диапазона. Неважно, 0–100 или 100–200, он все равно будет работать в одно и то же время. - person nickf; 10.10.2008
comment
@nickf: в вашем примере n == m (размер массива равен размеру всего возможного пространства ключей), поэтому O (n + m) - ›O (n + n) -› O (2 * n) - > На). - person jfs; 10.10.2008

Некоторое время назад я услышал об очень умном алгоритме сортировки от человека, который работал в телефонной компании. Им пришлось отсортировать огромное количество телефонных номеров. Пройдя через кучу различных стратегий сортировки, они наконец нашли очень элегантное решение: они просто создали битовый массив и обработали смещение в битовом массиве как номер телефона. Затем они просмотрели свою базу данных за один проход, изменив бит для каждого числа на 1. После этого они один раз просмотрели массив битов, выплевывая телефонные номера для записей, для которых бит был установлен на высокий уровень.

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

Я собираюсь на время опустить параметр n, b / c, который просто сбивает с толку - добавить смещение индекса довольно просто.

Учитывать:

for i = 0 to m
  if (a[a[i]]==a[i]) return false; // we have a duplicate
  while (a[a[i]] > a[i]) swapArrayIndexes(a[i], i)
  sum = sum + a[i]
next

if sum = (n+m-1)*m return true else return false

Это не O (n) - вероятно, ближе к O (n Log n) - но он обеспечивает постоянное пространство и может обеспечить другой вектор атаки для проблемы.

Если мы хотим O (n), то использование массива байтов и некоторых битовых операций обеспечит проверку дублирования с использованием дополнительных n / 32 байтов памяти (при условии, конечно, 32-битных целых).

РЕДАКТИРОВАТЬ: Вышеупомянутый алгоритм можно улучшить, добавив проверку суммы внутри цикла и проверив:

if sum > (n+m-1)*m return false

таким образом он быстро выйдет из строя.

person Community    schedule 07.10.2008
comment
Телефонная компания использовала простую сортировку по ведру. - person jfs; 08.10.2008
comment
Ага - и мой алгоритм выше выполняет сортировку по основанию (который, по словам Хьюгилла, равен O (n)), поэтому я бы сказал, что приведенное выше близко к оптимальному (если кто-то не может придумать доказательство для одного из статистических подходов). . - person Kevin Day; 09.10.2008
comment
Сумма может вылиться за край. После сортировки достаточно проверить max-min == m-1 - person jfs; 10.10.2008

Предполагая, что вам известна только длина массива, и вам разрешено изменять массив, это можно сделать в пространстве O (1) и времени O (n).

Этот процесс состоит из двух простых шагов. 1. "Сортировка по модулю" массива. [5,3,2,4] => [4,5,2,3] (O (2n)) 2. Убедитесь, что сосед каждого значения на единицу выше его (по модулю) (O (n))

Все сказано, что вам нужно не более 3 проходов через массив.

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

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

Комбинированный алгоритм: O (n) + O (2n) = O (3n) = O (n)

Псевдокод из моего решения:

foreach(values[]) 
  while(values[i] not congruent to i)
    to-be-evicted = values[i]
    evict(values[i])   // swap to its 'proper' location
    if(values[i]%length == to-be-evicted%length)
      return false;  // a 'duplicate' arrived when we evicted that number
  end while
end foreach
foreach(values[])
  if((values[i]+1)%length != values[i+1]%length)
    return false
end foreach

Я включил доказательство концепции в java-коде ниже, это некрасиво, но он проходит все модульные тесты, которые я для него сделал. Я называю их «StraightArray», потому что они соответствуют покерной руке стрита (непрерывная последовательность без учета масти).

public class StraightArray {    
    static int evict(int[] a, int i) {
        int t = a[i];
        a[i] = a[t%a.length];
        a[t%a.length] = t;
        return t;
    }
    static boolean isStraight(int[] values) {
        for(int i = 0; i < values.length; i++) {
            while(values[i]%values.length != i) {
                int evicted = evict(values, i);
                if(evicted%values.length == values[i]%values.length) {
                    return false;
                }
            }
        }
        for(int i = 0; i < values.length-1; i++) {
            int n = (values[i]%values.length)+1;
            int m = values[(i+1)]%values.length;
            if(n != m) {
                return false;
            }
        }
        return true;
    }
}
person Community    schedule 10.10.2008
comment
Каковы преимущества по сравнению с сортировкой по корзине на месте? См. http://stackoverflow.com/questions/177118/algorithm-to-determine-if-array-contains-nnm#177662 - person jfs; 10.10.2008
comment
При 3-х проходах нет необходимости использовать трюк «по модулю». Вычислите minval и maxval в первом проходе, затем поместите каждое целое число k в позицию (k-minval) во втором проходе, проверяя наличие конфликтов, как в исходном решении. Если условие выполнено, вы получите отсортированный массив. - person Rafał Dowgird; 10.10.2008

Реализация алгоритма Хаззена на C

#include<stdio.h>

#define swapxor(a,i,j) a[i]^=a[j];a[j]^=a[i];a[i]^=a[j];

int check_ntom(int a[], int n, int m) {
    int i = 0, j = 0;
    for(i = 0; i < m; i++) {
        if(a[i] < n || a[i] >= n+m) return 0;   //invalid entry
        j = a[i] - n;
        while(j != i) {
            if(a[i]==a[j]) return -1;           //bucket already occupied. Dupe.
            swapxor(a, i, j);                   //faster bitwise swap
            j = a[i] - n;
            if(a[i]>=n+m) return 0;             //[NEW] invalid entry
        }
    }
    return 200;                                 //OK
}

int main() {
    int n=5, m=5;
    int a[] = {6, 5, 7, 9, 8};
    int r = check_ntom(a, n, m);
    printf("%d", r);
    return 0;
}

Изменить: в код внесены изменения, устраняющие несанкционированный доступ к памяти.

person Community    schedule 26.07.2010
comment
Приведенный выше код не работает для [] = {6, 5, 7, 9, 10}; Когда массив выходит за границы после того, как он встречает замену «9» и «10». Возможно, проблема и с исходным алгоритмом? - person ignoramous; 26.07.2010

def test(a, n, m):
    seen = [False] * m
    for x in a:
        if x < n or x >= n+m:
            return False
        if seen[x-n]:
            return False
        seen[x-n] = True
    return False not in seen

print test([2, 3, 1], 1, 3)
print test([1, 3, 1], 1, 3)
print test([1, 2, 4], 1, 3)

Обратите внимание, что при этом выполняется только один проход через первый массив, не считая линейного поиска, задействованного в not in. :)

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

Обновление: Smashery указал, что я неправильно проанализировал «постоянный объем памяти», и это решение фактически не решает проблему.

person Community    schedule 07.10.2008
comment
Но это O (n) хранилище - вопрос требует O (1) хранилища. - person Smashery; 07.10.2008

Если вы хотите узнать сумму чисел [n ... n + m - 1], просто используйте это уравнение.

var sum = m * (m + 2 * n - 1) / 2;

Это работает для любого числа, положительного или отрицательного, даже если n является десятичным.

person Community    schedule 07.10.2008

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

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

Комментирование в ответе, потому что система еще не позволяет мне оставлять комментарии.

Обновление (в ответ на комментарии): в этом ответе я имел в виду пробел O (1), где ни слова «пробел» или «время» были опущены. Цитируемый текст является частью более раннего ответа, на который это ответ.

person Community    schedule 07.10.2008
comment
Обозначение Big-O не описывает пространство, необходимое для хранения информации. Суммирование массива - это алгоритм O (n), потому что вам нужно перебирать все элементы по одному разу, поэтому количество времени, необходимое для выполнения этого вычисления, увеличивается линейно с количеством элементов. - person nickf; 07.10.2008
comment
Нотация Big-O может использоваться как для временной, так и для пространственной сложности. Верно, если не указано иное, это означает время, но оба значения все еще действительны. - person Mark Ransom; 07.10.2008
comment
Если мы сложим два n-значных числа, мы можем получить (n + 1) -значную сумму. Эта дополнительная цифра требует дополнительного места. Чем больше чисел мы добавим, тем больше места может потребоваться. - person jfs; 10.10.2008
comment
В большинстве языков программирования число занимает фиксированное количество байтов независимо от количества цифр. Даже если бы он увеличился (например, в результате переключения с int на longit), он бы увеличивался так редко, что это не имело бы значения. - person CaptSolo; 16.10.2008

Учитывая это -

Напишите метод, который принимает массив int размера m ...

Я полагаю, будет справедливым заключить, что существует верхний предел для m, равный значению наибольшего int (обычно 2 ^ 32). Другими словами, даже если m не определено как int, тот факт, что массив не может иметь дубликатов, означает, что не может быть больше, чем количество значений, которые вы можете сформировать из 32 бит, которые, в свою очередь, подразумевает, что m также может быть int.

Если такой вывод приемлем, то я предлагаю использовать фиксированное пространство (2 ^ 33 + 2) * 4 байта = 34 359 738 376 байтов = 34,4 ГБ для обработки всех возможных случаев. (Не считая места, необходимого для входного массива и его цикла).

Конечно, для оптимизации я бы сначала принял во внимание m и выделил только фактическое необходимое количество (2m + 2) * 4 байта.

Если это приемлемо для ограничения пространства O (1) - для заявленной проблемы - тогда позвольте мне перейти к алгоритмическому предложению ... :)

Допущения: массив из m int, положительных или отрицательных, не более 4 байтов. Дубликаты обрабатываются. Первым значением может быть любое допустимое целое число. Ограничьте m, как указано выше.

Сначала создайте массив int длиной 2m-1, ary и укажите три переменные типа int: left, diff и вправо. Обратите внимание, что составляет 2m + 2 ...

Во-вторых, возьмите первое значение из входного массива и скопируйте его в позицию m-1 в новом массиве. Инициализируйте три переменные.

  • установить ary [m-1] - nthVal // n = 0
  • установите left = diff = right = 0.

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

  • установите diff = nthVal - ary [m-1]
  • if (diff> m-1 + right || diff ‹1-m + left) возвращает false / / за границами
  • if (ary [m-1 + diff]! = null) return false // дубликат
  • установите ary [m-1 + diff] = nthVal
  • if (diff> left) left = diff // ограничивает левую границу дальше вправо
  • if (diffright) right = diff // ограничивает правую границу дальше влево

Я решил записать это в код, и это сработало.

Вот рабочий пример с использованием C #:

public class Program
{
    static bool puzzle(int[] inAry)
    {
        var m = inAry.Count();
        var outAry = new int?[2 * m - 1];
        int diff = 0;
        int left = 0;
        int right = 0;
        outAry[m - 1] = inAry[0];
        for (var i = 1; i < m; i += 1)
        {
            diff = inAry[i] - inAry[0];
            if (diff > m - 1 + right || diff < 1 - m + left) return false;
            if (outAry[m - 1 + diff] != null) return false;
            outAry[m - 1 + diff] = inAry[i];
            if (diff > left) left = diff;
            if (diff < right) right = diff;
        }
        return true;
    }

    static void Main(string[] args)
    {
        var inAry = new int[3]{ 2, 3, 4 };
        Console.WriteLine(puzzle(inAry));
        inAry = new int[13] { -3, 5, -1, -2, 9, 8, 2, 3, 0, 6, 4, 7, 1 };
        Console.WriteLine(puzzle(inAry));
        inAry = new int[3] { 21, 31, 41 };
        Console.WriteLine(puzzle(inAry));
        Console.ReadLine();
    }

}
person Community    schedule 07.10.2008
comment
Может кто-нибудь объяснить, почему этот пост мог быть признан бесполезным? Я разместил свои предположения вместе со своим собственным оригинальным алгоритмом с рабочим кодом в качестве доказательства. - person hurst; 10.10.2008
comment
Небольшое примечание: согласно вашим предположениям m не int, а unsigned int. - person jfs; 22.11.2008

примечание: этот комментарий основан на исходном тексте вопроса (с тех пор он был исправлен)

Если вопрос поставлен в точности, как написано выше (и это не просто опечатка), и для массива размера n функция должна вернуть (True / False), если массив состоит из чисел 1 .. .n + 1,

... тогда ответ всегда будет ложным, потому что массив со всеми числами 1 ... n + 1 будет иметь размер n + 1, а не n. следовательно, на этот вопрос можно ответить за O (1). :)

person Community    schedule 07.10.2008
comment
Я сделал более очевидным, что ответ не имеет отношения к текущей версии вопроса. - person jfs; 08.10.2008

Контрпример для алгоритма XOR.

(не могу опубликовать это как комментарий)

@popopome

Для a = {0, 2, 7, 5,} он возвращает true (означает, что a является перестановкой диапазона [0, 4)), но в этом случае он должен возвращать false (a, очевидно, не является перестановкой [0, 4)).

Другой пример счетчика: {0, 0, 1, 3, 5, 6, 6} - все значения находятся в диапазоне, но есть дубликаты.

Я мог неправильно реализовать идею (или тесты) popopome, поэтому вот код:

bool isperm_popopome(int m; int a[m], int m, int  n)
{
  /** O(m) in time (single pass), O(1) in space,
      no restrictions on n,
      no overflow,
      a[] may be readonly
  */
  int even_xor = 0;
  int odd_xor  = 0;

  for (int i = 0; i < m; ++i)
    {
      if (a[i] % 2 == 0) // is even
        even_xor ^= a[i];
      else
        odd_xor ^= a[i];

      const int b = i + n;
      if (b % 2 == 0)    // is even
        even_xor ^= b;
      else
        odd_xor ^= b;
    }

  return (even_xor == 0) && (odd_xor == 0);
}
person Community    schedule 08.10.2008

Версия C псевдокода b3

(чтобы избежать неправильного толкования псевдокода)

Пример счетчика: {1, 1, 2, 4, 6, 7, 7}.

int pow_minus_one(int power)
{
  return (power % 2 == 0) ? 1 : -1;
}

int ceil_half(int n)
{
  return n / 2 + (n % 2);
}

bool isperm_b3_3(int m; int a[m], int m, int n)
{
  /**
     O(m) in time (single pass), O(1) in space,
     doesn't use n
     possible overflow in sum
     a[] may be readonly
   */
  int altsum = 0;
  int mina = INT_MAX;
  int maxa = INT_MIN;

  for (int i = 0; i < m; ++i)
    {
      const int v = a[i] - n + 1; // [n, n+m-1] -> [1, m] to deal with n=0
      if (mina > v)
        mina = v;
      if (maxa < v)
        maxa = v;

      altsum += pow_minus_one(v) * v;
    }
  return ((maxa-mina == m-1)
          and ((pow_minus_one(mina + m-1) * ceil_half(mina + m-1)
                - pow_minus_one(mina-1) * ceil_half(mina-1)) == altsum));
}
person Community    schedule 09.10.2008
comment
Вам нужно изменить (mina + m) на (mina + m - 1). - person b3.; 09.10.2008
comment
Я изменил (mina + m) - ›(mina + m-1). Теперь он ломается на {1, 1, 2, 2,} - person jfs; 10.10.2008
comment
Эта строка неверна и не указана в моем алгоритме. Мы не можем сделать предположение, что [n, n + m) - ›[1, m]: const int v = a [i] - n + 1; // [n, n + m) - ›[1, m] - person b3.; 10.10.2008
comment
@ b3: это не предположение, например {10, 12, 11} (где n = 10, m = 3) - ›{1, 3, 2} (где n = 1, m = 3). Любой алгоритм, отвечающий требованиям задачи, должен возвращать одинаковые ответы для обоих диапазонов. - person jfs; 10.10.2008
comment
В требованиях к задаче не указано, что задано n. Единственными входными данными являются длина вектора данных и сам вектор данных. Алгоритм, который я представил, не допускает n в качестве входных данных (что, я считаю, является более гибким решением, чем необходимость знать n заранее). - person b3.; 10.10.2008
comment
кстати, a[i]-n+1 не работает для {1,1,2,2}, т.е. даже без строки v=a[i]-n+1 алгоритм не работает. - person jfs; 10.10.2008
comment
Ты прав. Я удалил дополнительную проверку верхней и нижней границ после цикла, которая, как я считал, была ненужной, но на самом деле она необходима. См. Обновленный псевдокод: stackoverflow.com/questions/177118/ - person b3.; 10.10.2008
comment
Обновленная версия C для работы с n = 0, новый пример счетчика {1, 1, 2, 4, 6, 7, 7}. Теперь версия C не синхронизирована с псевдокодом - person jfs; 10.10.2008

В Python:

def ispermutation(iterable, m, n):
    """Whether iterable and the range [n, n+m) have the same elements.

       pre-condition: there are no duplicates in the iterable
    """ 
    for i, elem in enumerate(iterable):
        if not n <= elem < n+m:
            return False

    return i == m-1

print(ispermutation([1, 42], 2, 1)    == False)
print(ispermutation(range(10), 10, 0) == True)
print(ispermutation((2, 1, 3), 3, 1)  == True)
print(ispermutation((2, 1, 3), 3, 0)  == False)
print(ispermutation((2, 1, 3), 4, 1)  == False)
print(ispermutation((2, 1, 3), 2, 1)  == False)

Это O (м) во времени и O (1) в пространстве. Он не учитывает дубликаты.

Альтернативное решение:

def ispermutation(iterable, m, n): 
    """Same as above.

    pre-condition: assert(len(list(iterable)) == m)
    """
    return all(n <= elem < n+m for elem in iterable)
person Community    schedule 10.10.2008

МОЙ ТЕКУЩИЙ ЛУЧШИЙ ВАРИАНТ

def uniqueSet( array )
  check_index = 0; 
  check_value = 0; 
  min = array[0];
  array.each_with_index{ |value,index|
         check_index = check_index ^ ( 1 << index );
         check_value = check_value ^ ( 1 << value );
         min = value if value < min
  } 
  check_index =  check_index  << min;
  return check_index == check_value; 
end

O (n) и пробел O (1)

Я написал сценарий для комбинаций грубой силы, которые могут не сработать, но он не нашел ни одного. Сообщите, если у вас есть массив, который противоречит этой функции. :)


@ J.F. Себастьян

Это не настоящий алгоритм хеширования. Технически это высокоэффективный упакованный логический массив «видимых» значений.

ci = 0, cv = 0
[5,4,3]{ 
  i = 0 
  v = 5 
  1 << 0 == 000001
  1 << 5 == 100000
  0 ^ 000001  = 000001
  0 ^ 100000  = 100000

  i = 1
  v = 4 
  1 << 1 == 000010
  1 << 4 == 010000
  000001 ^ 000010  = 000011
  100000 ^ 010000  = 110000 

  i = 2
  v = 3 
  1 << 2 == 000100
  1 << 3 == 001000
  000011 ^ 000100  = 000111
  110000 ^ 001000  = 111000 
}
min = 3 
000111 << 3 == 111000
111000 === 111000

Дело в том, что для "подделки" большинства проблемных случаев используются дубликаты. В этой системе XOR наказывает вас за использование одного и того же значения дважды и предполагает, что вы сделали это 0 раз.

Здесь, конечно же, есть предостережения:

  1. длина входного массива и максимальное значение массива ограничены максимальным значением для $x в ( 1 << $x > 0 )
  2. конечная эффективность зависит от того, как ваша базовая система реализует возможности:

    1. shift 1 bit n places right.
    2. xor 2 регистра. (где "регистры" могут, в зависимости от реализации, охватывать несколько регистров)

    edit Обратите внимание, приведенные выше утверждения могут сбивать с толку. Предполагая идеальную машину, где «целое число» - это регистр с бесконечной точностью, который все еще может выполнить a ^ b за время O (1).

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

  • Насколько сложен 1 == 1? Разумеется, это должно быть O (1) каждый раз, верно ?.
  • А как насчет 2 ^ 32 == 2 ^ 32.
  • О (1)? 2 ^ 33 == 2 ^ 33? Теперь у вас есть вопрос о размере регистра и базовой реализации.
  • К счастью, XOR и == могут выполняться параллельно, поэтому, если кто-то предполагает бесконечную точность и машину, предназначенную для работы с бесконечной точностью, можно безопасно предположить, что XOR и == принимают постоянное время независимо от их значения (поскольку его бесконечная ширина, это будет иметь бесконечное заполнение 0. Очевидно, что этого не существует. Но также изменение 000000 на 000100 не увеличивает использование памяти.
  • Тем не менее, на некоторых машинах (1 ‹< 32) ‹< 1 будет потреблять больше памяти, но неизвестно, сколько.
person Community    schedule 07.10.2008
comment
Насколько я понимаю, ваше решение вычисляет какой-то 32-битный хеш. Но, как мы знаем, хеш-функции подвержены конфликтам, например, md5sum. Можете ли вы доказать, что коллизии невозможны при ограничениях вопроса? - person jfs; 08.10.2008
comment
Для вашего решения требуются дополнительные m бит, поэтому это неверно O (1) в пространстве. [m - размер (количество элементов) массива] - person jfs; 09.10.2008
comment
Я опубликовал C-версию stackoverflow.com/questions/177118/ - person jfs; 09.10.2008
comment
Я заметил, что мои 1-й и 2-й комментарии кажутся противоречащими друг другу. Чтобы уточнить: 1-й комментарий относится к реализации, основанной на конечных числах (например, int в C). Второй комментарий относится к целым числам с бесконечной точностью (например, к целым числам в Ruby). Кстати, как это было реализовано выше, требуется O (m + n) бит. - person jfs; 10.10.2008
comment
Я построил контрпример. См. Ссылку выше на версию C. - person jfs; 10.10.2008
comment
О сложности: описанные вами операции будут O (1) для представлений фиксированной ширины, например, 1 ‹---------------- 100 в C, но это не O (1) для вычислений с бесконечной точностью, например. 1 ‹---------------- 100 на Ruby. - person jfs; 10.10.2008

Версия C Ruby-решения Кента Фредрика

(для облегчения тестирования)

Контрпример (для версии C): {8, 33, 27, 30, 9, 2, 35, 7, 26, 32, 2, 23, 0, 13, 1, 6, 31, 3, 28, 4, 5, 18, 12, 2, 9, 14, 17, 21, 19, 22, 15, 20, 24, 11, 10, 16, 25}. Здесь n = 0, m = 35. Эта последовательность пропускает 34 и имеет два 2.

Это решение O (m) во времени и O (1) в пространстве.

Значения, выходящие за пределы допустимого диапазона, легко обнаруживаются в O (n) во времени и O (1) в пространстве, поэтому тесты сконцентрированы на последовательностях в пределах диапазона (означает, что все значения находятся в допустимом диапазоне [n, n+m)). В противном случае {1, 34} - пример счетчика (для версии C sizeof (int) == 4, стандартное двоичное представление чисел).

Основное различие между версией C и Ruby: оператор << будет вращать значения в C из-за конечного sizeof (int), но в Ruby числа будут расти, чтобы соответствовать результату, например,

Рубин: 1 << 100 # -> 1267650600228229401496703205376

C: int n = 100; 1 << n // -> 16

В Ruby: check_index ^= 1 << i; эквивалентно check_index.setbit(i). Тот же эффект можно было бы реализовать на C ++: vector<bool> v(m); v[i] = true;

bool isperm_fredric(int m; int a[m], int m, int n)
{
  /**
     O(m) in time (single pass), O(1) in space,
     no restriction on n,
     ?overflow?
     a[] may be readonly
   */
  int check_index = 0;
  int check_value = 0;

  int min = a[0];
  for (int i = 0; i < m; ++i) {

    check_index ^= 1 << i;
    check_value ^= 1 << (a[i] - n); //

    if (a[i] < min)
      min = a[i];
  }
  check_index <<= min - n; // min and n may differ e.g., 
                           //  {1, 1}: min=1, but n may be 0.
  return check_index == check_value;
}

Значения вышеупомянутой функции были протестированы по следующему коду:

bool *seen_isperm_trusted  = NULL;
bool isperm_trusted(int m; int a[m], int m, int n)
{
  /** O(m) in time, O(m) in space */

  for (int i = 0; i < m; ++i) // could be memset(s_i_t, 0, m*sizeof(*s_i_t));
    seen_isperm_trusted[i] = false;

  for (int i = 0; i < m; ++i) {

    if (a[i] < n or a[i] >= n + m)
      return false; // out of range

    if (seen_isperm_trusted[a[i]-n])
      return false; // duplicates
    else
      seen_isperm_trusted[a[i]-n] = true;
  }

  return true; // a[] is a permutation of the range: [n, n+m)
}

Входные массивы создаются с помощью:

void backtrack(int m; int a[m], int m, int nitems)
{
  /** generate all permutations with repetition for the range [0, m) */
  if (nitems == m) {
    (void)test_array(a, nitems, 0); // {0, 0}, {0, 1}, {1, 0}, {1, 1}
  }
  else for (int i = 0; i < m; ++i) {
      a[nitems] = i;
      backtrack(a, m, nitems + 1);
    }
}
person Community    schedule 09.10.2008
comment
Вы не можете назвать это рабочим решением, если оно ломается, если у вас есть последовательности длиннее 32. Искусственное ограничение решения не делает его O (1). - person Derek Park; 10.10.2008
comment
@Derek: Он работает с последовательностями длиннее 32. Но для некоторых из них он может возвращать неправильный ответ, но я еще не видел контрпримеров. Например, он работает на {8, 33, 27, 30, 9, 7, 26, 32, 2, 23, 0, 13, 1, 6, 31, 3, 28, 4, 5, 18, 12, 29. , 14, 17, 21, 19, 22, 15, 20, 24, 11, 10, 33, 25}. - person jfs; 10.10.2008
comment
Я добавил контрпример для версии C. - person jfs; 10.10.2008

Ответ от "nickf" не работает, если массив не отсортирован var_dump (testArray (array (5, 3, 1, 2, 4), 1, 5)); // дает "дубликаты" !!!!

Также ваша формула для вычисления суммы ([n ... n + m-1]) выглядит неверной .... правильная формула: (m (m + 1) / 2 - n (n-1) / 2)

person Community    schedule 21.11.2008
comment
Я реализовал метод nickf на C. Он отлично работает. Не знаю, верна ли его реализация, но, по крайней мере, верен метод. - person jfs; 22.11.2008

Массив содержит N чисел, и вы хотите определить, суммируются ли два из этих чисел с заданным числом K. Например, если введено 8,4, 1,6 и K равно 10, ответ будет положительным (4 и 6 ). Число можно использовать дважды. Сделайте следующее. а. Приведите алгоритм O (N2) для решения этой проблемы. б. Приведите алгоритм O (N log N) для решения этой проблемы. (Подсказка: сначала отсортируйте элементы. После этого вы сможете решить задачу за линейное время.) C. Кодируйте оба решения и сравните время работы ваших алгоритмов. 4.

person Community    schedule 31.01.2009

Произведение m последовательных чисел делится на m! [m факториал]


поэтому за один проход вы можете вычислить произведение m чисел, а также вычислить m! и посмотрим, есть ли произведение по модулю m! равен нулю в конце прохода

Возможно, я что-то упускаю, но вот что приходит мне на ум ...

что-то вроде этого в python

my_list1 = [9,5,8,7,6]

my_list2 = [3,5,4,7]

def последовательный (my_list):

count = 0
prod = fact = 1
for num in my_list:
    prod *= num
    count +=1 
    fact *= count
if not prod % fact: 
    return 1   
else:   
    return 0 

печать последовательного (my_list1)

печать последовательного (my_list2)


HotPotato ~ $ python m_consecutive.py

1

0

person Community    schedule 14.05.2009
comment
В названии указано, что если у вас есть m последовательных чисел, то произведение делится на m !. Это не позволяет сделать вывод, что у вас есть последовательные числа, если произведение делится на m !. [9,5,8,7,6] в вашем примере приводит к 1. То же самое и [18,5,8,7,6], но числа не идут подряд. - person Renze de Waal; 15.05.2009

Предлагаю следующее:

Выберите конечный набор простых чисел P_1, P_2, ..., P_K и вычислите вхождения элементов во входной последовательности (за вычетом минимума) по модулю каждого P_i. Образец действительной последовательности известен.

Например, для последовательности из 17 элементов по модулю 2 у нас должен быть профиль: [9 8], по модулю 3: [6 6 5], по модулю 5: [4 4 3 3 3] и т. Д.

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

S_i is an int array of size P_i, initially filled with 0, i=1..K
M is the length of the input sequence
Mn = INT_MAX
Mx = INT_MIN

for x in the input sequence:
  for i in 1..K: S_i[x % P_i]++  // count occurrences mod Pi
  Mn = min(Mn,x)  // update min
  Mx = max(Mx,x)  // and max

if Mx-Mn != M-1: return False  // Check bounds

for i in 1..K:
  // Check profile mod P_i
  Q = M / P_i
  R = M % P_i
  Check S_i[(Mn+j) % P_i] is Q+1 for j=0..R-1 and Q for j=R..P_i-1
  if this test fails, return False

return True
person Community    schedule 14.05.2009

Любой непрерывный массив [n, n + 1, ..., n + m-1] может быть отображен на «базовый» интервал [0, 1, ..., m] с помощью оператора по модулю. Для каждого i в интервале существует ровно один i% m в базовом интервале, и наоборот.

Любой непрерывный массив также имеет размер m (максимум - минимум + 1), равный его размеру.

Используя эти факты, вы можете создать «обнаруженный» логический массив того же размера, содержащий изначально все ложные данные, и при посещении входного массива установить для связанных «обнаруженных» элементов значение true.

Этот алгоритм составляет O (n) в пространстве, O (n) во времени и проверяет наличие дубликатов.

def contiguous( values )
    #initialization
    encountered = Array.new( values.size, false )
    min, max = nil, nil
    visited = 0

    values.each do |v|

        index = v % encountered.size

        if( encountered[ index ] )
            return "duplicates"; 
        end

        encountered[ index ] = true
        min = v if min == nil or v < min
        max = v if max == nil or v > max 
        visited += 1
    end

    if ( max - min + 1 != values.size ) or visited != values.size
        return "hole"
    else
        return "contiguous"
    end

end

tests = [ 
[ false, [ 2,4,5,6 ] ], 
[ false, [ 10,11,13,14 ] ] , 
[ true , [ 20,21,22,23 ] ] , 
[ true , [ 19,20,21,22,23 ] ] ,
[ true , [ 20,21,22,23,24 ] ] ,
[ false, [ 20,21,22,23,24+5 ] ] ,
[ false, [ 2,2,3,4,5 ] ]
]

tests.each do |t|
    result = contiguous( t[1] )
    if( t[0] != ( result == "contiguous" ) )
        puts "Failed Test : " + t[1].to_s + " returned " + result
    end
end
person Community    schedule 15.05.2009

Мне нравится идея Грега Хьюгилла о сортировке Radix. Чтобы найти дубликаты, вы можете выполнить сортировку за время O (N) с учетом ограничений на значения в этом массиве.

Для временного интервала O (1) O (N) на месте, который восстанавливает исходный порядок списка, вам не нужно выполнять фактическую замену этого числа; можно просто пометить его флажком:

//Java: assumes all numbers in arr > 1
boolean checkArrayConsecutiveRange(int[] arr) {

// find min/max
int min = arr[0]; int max = arr[0]
for (int i=1; i<arr.length; i++) {
    min = (arr[i] < min ? arr[i] : min);
    max = (arr[i] > max ? arr[i] : max);
}
if (max-min != arr.length) return false;

// flag and check
boolean ret = true;
for (int i=0; i<arr.length; i++) {
    int targetI = Math.abs(arr[i])-min;
    if (arr[targetI] < 0) {
        ret = false; 
        break;
    }
    arr[targetI] = -arr[targetI];
}
for (int i=0; i<arr.length; i++) {
    arr[i] = Math.abs(arr[i]);
}

return ret;
}

Хранение флагов внутри данного массива является своего рода обманом и плохо сочетается с распараллеливанием. Я все еще пытаюсь придумать способ сделать это, не касаясь массива за время O (N) и пространство O (log N). Проверка по сумме и по сумме наименьших квадратов (arr [i] - arr.length / 2.0) ^ 2 кажется, что это может сработать. Одна определяющая характеристика, которую мы знаем о массиве 0 ... m без дубликатов, заключается в том, что он равномерно распределен; мы должны просто проверить это.

Вот если бы я только мог это доказать.

Я хотел бы отметить, что приведенное выше решение с использованием факториала занимает пространство O (N) для хранения самого факториала. N! > 2 ^ N, для хранения которого требуется N байтов.

person Community    schedule 17.06.2009

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


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

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

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

function is_permutation($ints) {

  /* Gather some meta-data. These scans can
     be done simultaneously */
  $lowest = min($ints);
  $length = count($ints);

  $max_index = $length - 1;

  $sort_run_count = 0;

  /* I do not have any proof that running this sort twice
     will always completely sort the array (of course only
     intentionally happening if the array is a permutation) */

  while ($sort_run_count < 2) {

    for ($i = 0; $i < $length; ++$i) {

      $dest_index = $ints[$i] - $lowest;

      if ($i == $dest_index) {
        continue;
      }

      if ($dest_index > $max_index) {
        return false;
      }

      if ($ints[$i] == $ints[$dest_index]) {
        return false;
      }

      $temp = $ints[$dest_index];
      $ints[$dest_index] = $ints[$i];
      $ints[$i] = $temp;

    }

    ++$sort_run_count;

  }

  return true;

}
person Community    schedule 20.05.2010

Итак, есть алгоритм, который принимает O (n ^ 2), который не требует изменения входного массива и занимает постоянное пространство.

Во-первых, предположим, что вы знаете n и m. Это линейная операция, поэтому она не добавляет дополнительной сложности. Затем предположим, что существует один элемент, равный n, и один элемент, равный n+m-1, а все остальные находятся в [n, n+m). Учитывая это, мы можем свести проблему к наличию массива с элементами в [0, m).

Теперь, когда мы знаем, что элементы ограничены размером массива, мы можем рассматривать каждый элемент как узел с единственной ссылкой на другой элемент; другими словами, массив описывает ориентированный граф. В этом ориентированном графе, если нет повторяющихся элементов, каждый узел принадлежит циклу, то есть узел доступен от самого себя за m или меньше шагов. Если есть повторяющийся элемент, значит, существует один узел, который вообще недоступен для себя.

Итак, чтобы обнаружить это, вы проходите весь массив от начала до конца и определяете, возвращается ли каждый элемент сам к себе за <=m шагов. Если какой-либо элемент недостижим за <=m шагов, значит, у вас есть дубликат, и вы можете вернуть false. В противном случае, когда вы закончите посещать все элементы, вы можете вернуть true:

for (int start_index= 0; start_index<m; ++start_index)
{
    int steps= 1;
    int current_element_index= arr[start_index];
    while (steps<m+1 && current_element_index!=start_index)
    {
        current_element_index= arr[current_element_index];
        ++steps;
    }

    if (steps>m)
    {
        return false;
    }
}

return true;

Вы можете оптимизировать это, сохранив дополнительную информацию:

  1. Запишите сумму продолжительности цикла для каждого элемента, если цикл не посещает элемент перед этим элементом, назовите его sum_of_steps.
  2. Для каждого элемента вывести только m-sum_of_steps узлов. Если вы не вернетесь к начальному элементу и не посетите элемент перед начальным элементом, вы нашли цикл, содержащий повторяющиеся элементы, и можете вернуть false.

Это все еще O (n ^ 2), например {1, 2, 3, 0, 5, 6, 7, 4}, но это немного быстрее.

person Community    schedule 21.05.2010

ciphwn прав. Все дело в статистике. С точки зрения статистики, возникает вопрос: образует ли последовательность чисел дискретное равномерное распределение. Дискретное равномерное распределение - это когда все значения из конечного набора возможных значений равновероятны. К счастью, есть несколько полезных формул, позволяющих определить, является ли дискретный набор однородным. Во-первых, для определения среднего значения набора (a..b) будет (a + b) / 2, а дисперсия - (n.n-1) / 12. Затем определите дисперсию данного набора:

variance = sum [i=1..n] (f(i)-mean).(f(i)-mean)/n

а затем сравните с ожидаемой дисперсией. Для этого потребуется два прохода по данным: один раз для определения среднего значения, а второй - для расчета дисперсии.

Использованная литература:

person Community    schedule 07.10.2008
comment
Этот алгоритм не работает для [1,3,3,4,4,5,8,8] - person Stephen Denne; 09.10.2008
comment
Нет, для этого набора ожидаемое (т.е. набор [1,2,3,4,5,6,7,8]) и фактическое среднее значение равны 4,5, но дисперсия составляет 4,67 и 5,25 соответственно. - person Skizz; 21.10.2008
comment
Ой, мне там плохо. Я отмечаю, что forumla ошибается в основном сообщении, дисперсия для равномерного распределения составляет (n-1) (n + 1) / 12, и первый комментарий действительно сбивает с толку. - person Skizz; 21.10.2008

Вот решение за время O (N) и дополнительное пространство O (1) для поиска дубликатов: -

public static boolean check_range(int arr[],int n,int m) {

        for(int i=0;i<m;i++) {
            arr[i] = arr[i] - n;
            if(arr[i]>=m)
                return(false);
        }

        System.out.println("In range");

        int j=0;
        while(j<m) {
            System.out.println(j);
            if(arr[j]<m) {

                if(arr[arr[j]]<m) {

                    int t = arr[arr[j]];
                    arr[arr[j]] = arr[j] + m;
                    arr[j] = t;
                    if(j==arr[j]) {

                        arr[j] = arr[j] + m;
                        j++;
                    }

                }

                else return(false);

            }

            else j++;

        }

Объяснение: -

  1. Привести число в диапазон (0, m-1) с помощью arr [i] = arr [i] - n, если за пределами диапазона вернуть false.
  2. для каждого я проверяю, не занят ли arr [arr [i]], то есть ли он имеет значение меньше m
  3. если это так, поменяйте местами (arr [i], arr [arr [i]]) и arr [arr [i]] = arr [arr [i]] + m, чтобы указать, что он занят
  4. если arr [j] = j, просто добавьте m и увеличьте j
  5. если arr [arr [j]]> = m означает, что он занят, следовательно, текущее значение дублируется, следовательно, возвращается false.
  6. если arr [j]> = m, пропустить
person Community    schedule 11.02.2014

Если это была опечатка и вопрос заключается в том, что все числа находятся в диапазоне 1 ... n, тогда:

def try_arr(arr):
    n = len(arr)
    return (not any(x<1 or x>n for x in arr)) and sum(arr)==n*(n+1)/2

$ print try_arr([1,2,3])
True

$ print try_arr([1,3,1])
False

$ print try_arr([1,2,4])
False

Примечания:

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

  • Если размер массива (n) был известен, вы могли бы изменить его для потоковой передачи данных, например, из входного файла, и почти не использовать память (1 временная переменная внутри sum () и 1 переменная для текущего элемента, взятого из потока)

  • any () является новым в python 2.5 (но у вас есть альтернативные способы выразить то же самое в более ранних версиях python)

  • он использует O (n) время O (1) пространство. (обновление: я написал, что он учитывает дубликаты, но, по-видимому, это неправда, что демонстрируется комментарием к другому ответу здесь).

person Community    schedule 07.10.2008

Fail := False;
Sum1 := 0;
Sum2 := 0;
TSum1 := 0;
TSum2 := 0;

For i := 1 to m do
  Begin
    TSum1 := TSum1 + i;
    TSum2 := TSum2 + i * i;
    Item := Array[i] - n;
    If (Item < 0) or (Item >= m) then 
      Fail := True
    Else 
      Begin
        Sum1 := Sum1 + Item;
        Sum2 := Sum2 + Item * Item;
      End;
  End;
Fail := Fail Or (Sum1 <> TSum1) or (Sum2 <> TSum2);

Устали и нет компилятора, но я думаю, что это дает время выполнения O (m) и не обманешь.

person Community    schedule 07.10.2008
comment
Это довольно элегантно, но доказуемо ли это математически? Подразумевается, что для данного m существует только один ряд длины m, который приведет к данной комбинации суммы и суммы квадратов этого ряда. Я погуглил, но не нашел никаких доказательств или дополнительных ссылок ... - person Kevin Day; 07.10.2008
comment
См. Мои контрпримеры выше. - person Greg Hewgill; 07.10.2008

Похоже, что мы могли бы проверить наличие дубликатов, умножив все числа n ... n + m вместе, а затем сравнив это значение с ожидаемым продуктом последовательности без дубликатов m! / (N-1)! < / strong> (обратите внимание, что это предполагает, что последовательность не может пройти как тест ожидаемой суммы , так и тест ожидаемого продукта).

Итак, добавив к псевдокоду hazzen, мы получим:

is_range(int[] nums, int n, int m) {
  sum_to_m := (m * (m + 1)) / 2
  expected_sum := sum_to_m - (n * (n - 1)) / 2
  real_sum := sum(nums)
  expected_product := m! / (n - 1)!
  real_product := product(nums)
  return ((real_sum == expected_sum) && (expected_product == real_product))


РЕДАКТИРОВАТЬ: Вот мое решение на Java, использующее сумму квадратов для проверки дубликатов. Он также обрабатывает любой диапазон (включая отрицательные числа), сдвигая последовательность так, чтобы она начиналась с 1.

// low must be less than high
public boolean isSequence(int[] nums, int low, int high) {
    int shift = 1 - low;
    low += shift;
    high += shift;

    int sum = 0;
    int sumSquares = 0;
    for (int i = 0; i < nums.length; i++) {
        int num = nums[i] + shift;

        if (num < low)
            return false;
        else if (num > high)
            return false;

        sum += num;
        sumSquares += num * num;
    }

    int expectedSum = (high * (high + 1)) / 2;

    if (sum != expectedSum)
        return false;

    int expectedSumSquares = high * (high + 1) * (2 * high + 1) / 6;

    if (sumSquares != expectedSumSquares)
        return false;

    return true;
}
person Community    schedule 07.10.2008
comment
[2,2,4,6,6] = ›? (при условии, что вы знаете m, а не n) - person Kent Fredric; 07.10.2008
comment
Вы можете выполнить начальный проход, чтобы определить n и вычесть из него каждый элемент. Вопрос в том, можно ли построить последовательность для прохождения как суммы, так и теста продукта, чего я не знаю. Но это самое многообещающее, что у нас есть. - person Kyle Cronin; 07.10.2008
comment
Ожидаемый продукт плохо масштабируется, если вы не допускаете числа с бесконечной точностью; если вы это сделаете, вы по существу используете больше, чем O (N) пространство для представления этих чисел. Это дает мне идею попробовать что-то вроде суммирования n ^ 2 до m ^ 2 (которое имеет известную формулу). Не уверен в реквизитах на нем - person hazzen; 07.10.2008
comment
Ты прав, Хаззен, это не очень хорошо масштабируется. Может быть, вместо факториала, если мы используем сумму квадратов первых n натуральных чисел n (n + 1) (2n + 1) / 6, это сработает лучше. - person David Crow; 07.10.2008
comment
Ну да ладно, хватит использовать сумму квадратов. : p Я вижу, что выше было приведено несколько контрпримеров. Есть ли у кого-нибудь другие математические приемы, которые мы еще не пробовали? - person David Crow; 07.10.2008
comment
Когда одно из чисел равно нулю, ваш ожидаемый результат будет нулевым. - person EvilTeach; 08.10.2008
comment
Контрпример для тестов суммы и продукта: {1, 2, 4, 4, 4, 5, 7, 9, 9} (sum = 45, product = 362880) - person jfs; 12.10.2008

Как насчет использования XOR с четными и нечетными числами отдельно. Подумайте о битовом уровне, а не о самом целочисленном значении.

bool is_same(const int* a, const int* b, int len)
{
    int even_xor = 0; 
    int odd_xor = 0;

    for(int i=0;i<len;++i)
    {
        if(a[i] & 0x01) odd_xor ^= a[i];
        else even_xor ^= a[i];

        if(b[i] & 0x01) odd_xor ^= b[i];
        else even_xor ^= b[i];
    }

    return (even_xor == 0) && (odd_xor == 0);
}
person Community    schedule 07.10.2008
comment
Пример счетчика: {0, 2, 7, 5,}. Смотрите мой ответ. - person jfs; 09.10.2008
comment
Другой пример счетчика, где все значения находятся в диапазоне: {0, 0, 1, 3, 5, 6, 6} - person jfs; 09.10.2008
comment
Я опубликовал адаптированную (для вопроса OP) версию stackoverflow.com/questions/177118/ - person jfs; 09.10.2008

Не думаю, что я хорошо объяснил себя в своем исходном посте (под сплошной линией). Например, для ввода [1 2 3 4 5] алгоритм вычисляет сумму:

-1 + 2 - 3 + 4 - 5 

который должен быть равен

-1^5 * ceil(5/2)

Приведенный ниже псевдокод показывает, как проверяются векторы, которые не начинаются с 1. Алгоритм обрабатывает случаи, когда входной вектор не отсортирован и / или содержит дубликаты.


Следующий алгоритм решает проблему путем вычисления переменных сумм элементов вектора:

-1 + 2 - 3 + 4 - 5 + .... + m = (-1)^m * ceil(m/2)

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

function test(data, m)
    altSum = 0
    n = Inf
    mCheck = -Inf
    for ii = 1:m
    {
        if data(ii) < n
            n = data(ii)
        if data(ii) > mCheck
            mCheck = data(ii)
        altSum = altSum + (-1)^data(ii) * data(ii)
    }
    if ((mCheck-n+1!=m) || (-1)^(n+m-1) * ceil((n+m-1)/2) - ((-1)^(n-1) * ceil((n-1)/2)) != altSum
        return false
    else
        return true
person Community    schedule 07.10.2008
comment
Пример счетчика: {1, 1, 1, 2, 2} - person jfs; 09.10.2008
comment
Спасибо за встречный пример. Я неправильно реализовал алгоритм. Теперь это исправлено. - person b3.; 09.10.2008
comment
ceil((n+m+1/2)) это: (n + m + 1) // 2 + ((n + m + 1)% 2), где // целочисленное деление, а % - деление по модулю? - person jfs; 09.10.2008
comment
Эта версия не проходит {1,2,3, 4, 5, 6, 7}. Я опубликую версию на C, чтобы избежать неправильной интерпретации псевдокода. - person jfs; 09.10.2008
comment
Аааа, вот что происходит, когда я не пью утренний кофе. Он должен был читать n + m-1, а не n + m + 1. Ваша интерпретация функции ceil верна. - person b3.; 09.10.2008
comment
Пример счетчика: {1, 1, 2, 2,} - person jfs; 10.10.2008
comment
{1,1,2,2} не является контрпримером. Алгоритм выдает ожидаемый ответ для этого ввода. - person b3.; 10.10.2008
comment
Обновленный алгоритм дает действительный результат для {1,1,2,2}, но не работает на {0, 1, 2}. Пример нового счетчика {0,1,2}. Кстати, может быть невозможно (с математической точки зрения) обнаружить дубликаты за O (N) времени и O (1) в пространстве. - person jfs; 10.10.2008
comment
Я обновил версию C, чтобы она совпадала с псевдокодом. stackoverflow.com / questions / 177118 / - person jfs; 10.10.2008
comment
Как вы заметили, этот алгоритм действителен только для входов ›= 1. Я думаю о том, как поступить с‹ 1, не зная заранее n. Уловка, чтобы оставаться в пространстве O (1), заключается в том, чтобы иметь дело с различиями между числами, а не с суммами чисел ... - person b3.; 10.10.2008
comment
Я обновил версию C для работы с {0, ...}. Теперь он не работает на {1, 1, 2, 4, 6, 7, 7}, кстати, версия XOR popopome ломается и на нем. - person jfs; 10.10.2008

Полагаю, вопрос сводится к тому, чтобы

(maximum - minimum + 1) == array_size

и это, очевидно, можно сделать за O (N) время и O (1) пространство следующим образом:

int check_range(int input[], int N){
    int max = -INFINITY, min = INFINITY, i;
    for(i=0; i<N; i++){
        if(input[i] < min) min=input[i];
        if(input[i] > max) max=input[i];
    }
    return (max - min + 1) == N;
}

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

person Community    schedule 22.10.2011

Линейное во времени, постоянное в пространстве решение для int m

Постоянное пространство достигается за счет использования знакового бита. Это можно сделать для любого изменяемого диапазона int, если m меньше INT_MAX, т.е. когда диапазон ввода [n, n+m) может быть сдвинут в диапазон [1, m+1), если n не положительно. На практике предусловие почти всегда выполняется, если ввод изменяемый.

/** gcc -std=c99 ... */
#include <assert.h>
#include <iso646.h>  // and, or
#include <limits.h>  // INT_MIN
#include <stdbool.h> // bool
#include <stdlib.h>  // abs()

bool inrange(int m; int a[m], int m, int n)
{
  /** Whether min(a[]) == n and max(a[]) == n+(m-1)
  */
  if (m == 0) return true; // empty range is a valid range

  // check out-of-range values
  int max = INT_MIN, min = INT_MAX;
  for (int i = 0; i < m; ++i) {
    if (min > a[i]) min = a[i];
    if (max < a[i]) max = a[i];
  }
  return (min == n and max == n+(m-1));
}

bool isperm_minus2(int m; int a[m], int m, int n)
{
  /** O(m) in time, O(1) in space (for 'typeof(m) == typeof(*a) == int')

      Whether a[] is a permutation of the range [n, n+m).

      feature: It marks visited items using a sign bit.
  */
  if (not inrange(a, m, n))
    return false; // out of range

  assert((INT_MIN - (INT_MIN - 1)) == 1); // check n == INT_MIN
  for (int *p = a; p != &a[m]; ++p) {
    *p -= (n - 1); // [n, n+m) -> [1, m+1)
    assert(*p > 0);
  }

  // determine: are there duplicates
  bool has_duplicates = false;
  for (int i = 0; i < m; ++i) {
    const int j = abs(a[i]) - 1;
    assert(j >= 0);
    assert(j < m);
    if (a[j] > 0)
      a[j] *= -1; // mark
    else { // already seen
      has_duplicates = true;
      break;
    }
  }

  // restore the array
  for (int *p = a; p != &a[m]; ++p) {
    if (*p < 0) 
      *p *= -1; // unmark
    // [1, m+1) -> [n, n+m)
    *p += (n - 1);        
  }

  return not has_duplicates; // no duplicates? (+ inrange)
}
person Community    schedule 22.11.2008

Я не думаю, что вам вообще нужно использовать суммы. Просто проверьте минимум и максимум и проверьте на дури. Проверка на дублирование - более сложная часть, поскольку вы не знаете n заранее, поэтому вы не можете выполнить сортировку за один проход. Чтобы обойти это, ослабьте условие для массива (edit: destination). Вместо того, чтобы требовать его сортировки, используйте циклический сдвиг отсортированной последовательности, чтобы массив был [k, k + 1, ..., n + m-2, n + m-1, n, n + 1, ..., k-2, k-1] для некоторого k.

С указанным выше условием вы можете предположить, что [0] уже находится в правильной позиции, тогда правильная позиция для элемента d - это (d-a[0]) mod m, при условии, что индексирование массива начинается с нуля. Например, с [4,?,?,?] Вы можете ожидать [4,5,6,7] или [4,1,2,3] или [4,5,6,3] или [4,5, 2,3].

Затем просто просканируйте массив один раз, поместив каждый элемент в его расчетную позицию, обновив минимальное и максимальное значение и проверив наличие конфликтов. Если нет коллизий и max-min = m, то условие выполняется, в противном случае оно ложно.

person Community    schedule 09.10.2008
comment
А как насчет [4, 2, 1, 3]? Это соответствует требованиям OP, но это не циклический сдвиг отсортированной последовательности. - person jfs; 09.10.2008
comment
Кроме того, ваше решение не работает для последовательностей, доступных только для чтения (для таких случаев это не O (1) в пространстве). - person jfs; 09.10.2008
comment
[4,2,1,3] будут «отсортированы» алгоритмом в [4,1,2,3]. Мне не нужно, чтобы массив original сортировался таким образом. Для последовательностей только для чтения, вероятно, невозможно проверить дублирование за время O (m) и постоянную память. - person Rafał Dowgird; 09.10.2008
comment
Каковы преимущества этой версии по сравнению с сортировкой сегментов на месте, например, stackoverflow.com/questions/177118/ - person jfs; 10.10.2008
comment
Bucketsort предполагает, что вы заранее знаете диапазон целых чисел. Эта версия работает за один проход без этого предположения. Я перечитал вопрос, он фактически не указывает, является ли диапазон частью входных данных или нет. - person Rafał Dowgird; 10.10.2008

person    schedule
comment
Фактически это и происходит здесь [stackoverflow.com/questions/177118/. Это "вероятно" в вашем комментарии меня беспокоит ... - person Kevin Day; 07.10.2008
comment
См. Мои контрпримеры выше. - person Greg Hewgill; 07.10.2008
comment
См. Объяснение ниже (300 символов недостаточно!) - person Skizz; 07.10.2008
comment
См. Контрпример под сообщением @ Skizz - person jpalecek; 15.05.2012