Алгоритм для определения, содержит ли массив n... n + m?

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

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

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

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

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

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

Ответ 1

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

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

Идея в псевдокоде:
0) Начните с N = 0
1) Возьмите N-й элемент в списке.
2) Если он не находится в правильном месте, если список был отсортирован, проверьте, где он должен быть.
3) Если место, где оно должно быть уже, имеет одинаковое число, у вас есть обман - RETURN TRUE
4) В противном случае поменяйте номера (чтобы положить первый номер в нужное место).
5) С номером, который вы только что обменялись, находится ли он в нужном месте?
6) Если нет, вернитесь к шагу два.
7) В противном случае начните с первого шага с 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 или меньше. Если вам нужен пример, который имеет собственные значения min и max, попробуйте эту длину длиной 8:

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

Упрощенный подход (модификация идеи hazzen):

Integer массив длины m содержит все числа от n до n + m-1 ровно один раз, если f

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

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

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

  • Для всех индексов массива я от 0 до m-1 do
    • Если массив [i] n или array [i] >= n + m = > RETURN FALSE ( "значение вне диапазона найдено" )
    • Вычислить j = array [i] - n (это позиция на основе 0 массива [i] в ​​отсортированном массиве со значениями от n до n + m-1)
    • Пока j не равен i
      • Если список [i] равен списку [j] = > RETURN FALSE ( "duplicate found" )
      • Список подкачки [i] со списком [j]
      • Пересчитать j = array [i] - n
  • RETURN TRUE

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

Ответ 2

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

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

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

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 в списке.

Ответ 3

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

Упрощенный метод:

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

Шаг 2, выполните итерацию по списку, отслеживайте самые низкие и самые высокие.

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

Ответ 4

Любой однопроходный алгоритм требует хранения 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, то алгоритм не может правильно ответить на входы

A A '- yes

B A '- no

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

Q.E.D.

Ответ 5

В то же время я слышал о очень умном алгоритме сортировки от того, кто работал в телефонной компании. Им пришлось сортировать огромное количество телефонных номеров. Пройдя кучу разных стратегий сортировки, они наконец наткнулись на очень элегантное решение: они просто создали бит-массив и обработали смещение в массиве бит в качестве номера телефона. Затем они пронеслись через свою базу данных за один проход, изменив бит для каждого номера до 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 битных ints).

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

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

таким образом он быстро не работает.

Ответ 6

Проголосуйте, если я ошибаюсь, но я думаю, что мы можем определить, есть ли дубликаты или не использовать дисперсию. Поскольку мы знаем среднее значение заранее (n + (m-1)/2 или что-то подобное), мы можем просто суммировать числа и квадрат разницы, чтобы означать, совпадает ли сумма с уравнением (mn + m (m-1 )/2), а дисперсия равна (0 + 1 + 4 +... + (m-1) ^ 2)/m. Если дисперсия не соответствует, вероятно, у нас есть дубликат.

Предполагаемая дисперсия EDIT: должна быть (0 + 1 + 4 +... + [(m-1)/2] ^ 2) * 2/m, поскольку половина элементов меньше среднего, а другая половина больше среднего.

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

Ответ 7

Здесь рабочее решение в 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

Ответ 8

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

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

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

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

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

Комбинированный алгоритм 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;
    }
}

Ответ 9

Реализация алгоритма Хазцена в 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;
}

Изменить: изменение кода в целях устранения незаконного доступа к памяти.

Ответ 10

boolean determineContinuousArray(int *arr, int len)
{
    // Suppose the array is like below:
    //int arr[10] = {7,11,14,9,8,100,12,5,13,6};
    //int len = sizeof(arr)/sizeof(int);

    int n = arr[0];

    int *result = new int[len];
    for(int i=0; i< len; i++)
            result[i] = -1;
    for (int i=0; i < len; i++)
    {
            int cur = arr[i];
            int hold ;
            if ( arr[i] < n){
                    n = arr[i];
            }
            while(true){
                    if ( cur - n >= len){
                            cout << "array index out of range: meaning this is not a valid array" << endl;
                            return false;
                    }
                    else if ( result[cur - n] != cur){
                            hold = result[cur - n];
                            result[cur - n] = cur;
                            if (hold == -1) break;
                            cur = hold;

                    }else{
                            cout << "found duplicate number " << cur << endl;
                            return false;
                    }

            }
    }
    cout << "this is a valid array" << endl;
    for(int j=0 ; j< len; j++)
            cout << result[j] << "," ;
    cout << endl;
    return true;
}

Ответ 11

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 отметил, что я неправильно разобрал "постоянный объем памяти", и это решение на самом деле не решает проблему.

Ответ 12

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

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)

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


@J.F. Sebastian

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

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 раз.

Оговорки здесь, конечно,:

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

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

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

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

  • Насколько сложным является 1 == 1?, конечно, это должно быть O (1) каждый раз вправо?.
  • Что насчет 2 ^ 32 == 2 ^ 32.
  • O (1)? 2 ^ 33 == 2 ^ 33? Теперь у вас есть вопрос о размере регистра и основной реализации.
  • К счастью, XOR и == могут выполняться параллельно, поэтому, если вы принимаете бесконечную точность и машину, предназначенную для работы с бесконечной точностью, можно предположить, что XOR и == принимают постоянное время независимо от их значения (поскольку его бесконечная ширина, у него будет бесконечное заполнение 0. Очевидно, этого не существует. Но также смена 000000 на 000100 не увеличивает использование памяти.
  • Однако на некоторых машинах (1 < 32) < 1 будет потреблять больше памяти, но насколько неопределен.

Ответ 13

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

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

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

Ответ 14

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

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

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

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

Ответ 15

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

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

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

Ответ 16

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

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

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

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

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

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

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

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

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

  • set ary [m-1] - nthVal//n = 0
  • set left= diff= справа= 0

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

  • set diff= nthVal - ary [m-1]
  • if (diff > m-1 + справа || diff < 1-m + left) return false//вне границ
  • if ( ary [m-1 + diff]!= null) return false//duplicate
  • set ary [m-1 + diff] = nthVal
  • if (diff слева) левый= diff//ограничивает левую границу справа далее
  • if (diff < справа) справа= diff//ограничивает правосвязанные дальнейшие левые li >

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

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

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();
    }

}

Ответ 17

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

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

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

Литература:

Ответ 18

Counter-example для алгоритм 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);
}

Ответ 19

Версия C Решение Kent Fredric Ruby

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

Противоположный пример (для версии С): {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 будет расти, чтобы разместить результат, например,

Ruby: 1 << 100 # -> 1267650600228229401496703205376

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

В Ruby: check_index ^= 1 << i; эквивалентно check_index.setbit(i). Тот же эффект может быть реализован в С++: 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);
    }
}

Ответ 20

Версия 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));
}

Ответ 21

В 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 (m) во времени и 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)

Ответ 22

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

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

Ответ 23

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

Ответ 24

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


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

Мне может что-то не хватает, но это то, что приходит мне на ум...

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

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

my_list2 = [3,5,4,7]

def nextecutive (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

Ответ 25

Я предлагаю следующее:

Выберите конечное множество простых чисел 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

Ответ 26

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

Любой смежный массив также имеет "span" 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

Ответ 27

Мне нравится идея Грега Хьюджилла о сортировке 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 байтов для хранения.

Ответ 28

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


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

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

Я не очень люблю человека с псевдокодом, и я действительно думаю, что код может просто иметь больше смысла, чем слова. Вот реализация, которую я написал в 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;

}

Ответ 29

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

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

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

Итак, чтобы обнаружить это, вы перемещаете весь массив от начала до конца и определяете, возвращается ли каждый элемент к себе в шагах <=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;

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

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

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

Ответ 30

Вот решение в 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++;

        }

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

  • Привести число к диапазону (0, m-1) by arr [i] = arr [i] - n, если вне диапазона возвращает false.
  • для каждого я проверяем, является ли arr [arr [i]] незанятым, то есть имеет значение меньше m
  • если это так: swap (arr [i], arr [arr [i]]) и arr [arr [i]] = arr [arr [i]] + m, чтобы сигнализировать, что он занят
  • если arr [j] = j и просто добавьте m и приращение j
  • если arr [arr [j]] >= m означает, что он занят, поэтому текущее значение дублируется, поэтому возвращает false.
  • если arr [j] >= m, а затем пропустите