Ответ 1
Эта проблема не такая тривиальная, как люди видят. Домашнее задание? LOL.
Следующая ссылка имеет решение: http://arxiv.org/abs/0805.1598
Я столкнулся с следующим образцом задания на собеседование. Как я могу его решить?
Предположим, что у нас есть массив a1, a2,..., an, b1, b2,..., bn.
Цель состоит в том, чтобы изменить этот массив на a1, b1, a2, b2,..., an, bn в O (n) времени и в O (1) пространстве. Другими словами, нам нужен алгоритм с линейным временем для изменения массива на месте, с не более чем постоянным количеством дополнительного хранилища.
Эта проблема не такая тривиальная, как люди видят. Домашнее задание? LOL.
Следующая ссылка имеет решение: http://arxiv.org/abs/0805.1598
Это последовательность и заметки, которые я разработал с ручкой и бумагой. Я думаю, что это или вариация будет иметь место для любого большего n.
Каждая строка представляет собой другой шаг, и() означает, что перемещается этот шаг, и [] - это то, что было перенесено с последнего шага. Сам массив используется как хранилище, и для определения того, что двигаться дальше, требуется два указателя (один для L и один для N). L означает "строка письма", а N - "номер строки" (что перемещается).
A B C D 1 2 3 4 L A B C (D) 1 2 3 4 first is L, no need to move last N N A B C (3) 1 2 [D] 4 L A B (C) 2 1 [3] D 4 N A B 1 (2) [C] 3 D 4 L A (B) 1 [2] C 3 D 4 N A (1)[B] 2 C 3 D 4 A [1] B 2 C 3 D 4 done, no need to move A
Обратите внимание на переменные "прыжки указателя" - L-указатель всегда уменьшает на 1 (поскольку его нельзя съесть быстрее), но указатель N прыгает в соответствии с тем, "если он" заменил себя "(в пятне, спрыгните на два ) или если он что-то поменял (не прыгайте так, чтобы следующее что-то могло получить!)
YMMV
Эта проблема не так проста, как кажется, но, по некоторым соображениям, алгоритм для этого не так уж плох. Вы заметите, что первый и последний элементы уже на месте, поэтому нам не нужно беспокоиться о них. Мы будем хранить левую индексную переменную, которая представляет первый элемент в первой половине массива, который нуждается в изменении. После этого мы устанавливаем правую индексную переменную на первый элемент во второй половине массива, который необходимо изменить. Теперь все, что мы делаем, сворачивает элемент по правому индексу вниз один за другим, пока не достигнет левого индекса. Увеличьте левый индекс на 2 и правый индекс на 1 и повторите до тех пор, пока индексы не пересекаются или левый не пройдет справа от индекса (правый индекс всегда будет заканчиваться последним индексом массива). Мы увеличиваем левый индекс на два каждый раз, потому что элемент слева + 1 уже естественно встал на место.
protected void Interleave(int[] arr)
{
int left = 1;
int right = arr.Length / 2;
int temp;
while (left < right)
{
for (int i = right; i > left; i--)
{
temp = arr[i];
arr[i] = arr[i - 1];
arr[i - 1] = temp;
}
left += 2;
right += 1;
}
}
В этом алгоритме используется хранилище O (1) (с переменной temp, которая может быть устранена с использованием метода сложения/вычитания). Я не очень хорошо разбираюсь в анализе времени выполнения, но я считаю, что это все еще O (n) хотя мы выполняем много свопов. Возможно, кто-то может продолжить изучение анализа времени выполнения.
Во-первых, теория: переупорядочить элементы в "циклах перестановок". Возьмите элемент и поместите его на новое место, вытеснив находящийся в нем элемент. Затем вы берете этот перемещенный элемент и помещаете его в новое положение. Это вытесняет еще один элемент, поэтому полоскание и повторение. Если перемещенный элемент принадлежит к позиции элемента, с которого вы сначала начали, вы завершили один цикл.
Собственно, ваш - частный случай вопроса, который я задал здесь , который был: Как вы переставляете массив в любой заданный порядок в O (N) раз и O (1) пространство? В моем вопросе переупорядоченные позиции описываются массивом чисел, где число в n-й позиции указывает индекс элемента в исходном массиве.
Однако у вас нет этого дополнительного массива в вашей проблеме, и для его выделения потребуется O (N). К счастью, мы можем вычислить значение любого элемента в этом массиве "на лету", например:
int rearrange_pos(int x) {
if (x % 2 == 0) return x / 2;
else return (x - 1) / 2 + n; // where n is half the size of the total array
}
Я не буду дублировать сам алгоритм переупорядочения; его можно найти в принятом ответе на мой вопрос.
Изменить: Как указал Джейсон, ответ, который я связал, по-прежнему требует выделения массива bools, что делает его O (N). Это связано с тем, что перестановка может состоять из нескольких циклов. Я пытаюсь устранить необходимость в этом массиве для вашего особого случая, но безуспешно.. Кажется, что нет подходящего шаблона. Может быть, здесь кто-то может помочь вам.
Он вызвал проблему с местом на месте. Вот его реализация в С++ на основе здесь.
void in_place_in_shuffle(int arr[], int length)
{
assert(arr && length>0 && !(length&1));
// shuffle to {5, 0, 6, 1, 7, 2, 8, 3, 9, 4}
int i,startPos=0;
while(startPos<length)
{
i=_LookUp(length-startPos);
_ShiftN(&arr[startPos+(i-1)/2],(length-startPos)/2,(i-1)/2);
_PerfectShuffle(&arr[startPos],i-1);
startPos+=(i-1);
}
// local swap to {0, 5, 1, 6, 2, 7, 3, 8, 4, 9}
for (int i=0; i<length; i+=2)
swap(arr[i], arr[i+1]);
}
// cycle
void _Cycle(int Data[],int Lenth,int Start)
{
int Cur_index,Temp1,Temp2;
Cur_index=(Start*2)%(Lenth+1);
Temp1=Data[Cur_index-1];
Data[Cur_index-1]=Data[Start-1];
while(Cur_index!=Start)
{
Temp2=Data[(Cur_index*2)%(Lenth+1)-1];
Data[(Cur_index*2)%(Lenth+1)-1]=Temp1;
Temp1=Temp2;
Cur_index=(Cur_index*2)%(Lenth+1);
}
}
// loop-move array
void _Reverse(int Data[],int Len)
{
int i,Temp;
for(i=0;i<Len/2;i++)
{
Temp=Data[i];
Data[i]=Data[Len-i-1];
Data[Len-i-1]=Temp;
}
}
void _ShiftN(int Data[],int Len,int N)
{
_Reverse(Data,Len-N);
_Reverse(&Data[Len-N],N);
_Reverse(Data,Len);
}
// perfect shuffle of satisfying [Lenth=3^k-1]
void _PerfectShuffle(int Data[],int Lenth)
{
int i=1;
if(Lenth==2)
{
i=Data[Lenth-1];
Data[Lenth-1]=Data[Lenth-2];
Data[Lenth-2]=i;
return;
}
while(i<Lenth)
{
_Cycle(Data,Lenth,i);
i=i*3;
}
}
// look for 3^k that nearnest to N
int _LookUp(int N)
{
int i=3;
while(i<=N+1) i*=3;
if(i>3) i=i/3;
return i;
}
Тест:
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int length = sizeof(arr)/sizeof(int);
in_place_in_shuffle(arr, length);
После этого arr[]
будет {0, 5, 1, 6, 2, 7, 3, 8, 4, 9}
.
Если вы можете сначала преобразовать массив в связанный список, проблема станет тривиальной.