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