Этот вопрос может быть старым, но я не мог придумать ответа.
Скажем, есть два списка разной длины, слияния в точке; как узнать, где находится точка слияния?
Условия:
- Мы не знаем длину
- Мы должны анализировать каждый список только один раз.
Этот вопрос может быть старым, но я не мог придумать ответа.
Скажем, есть два списка разной длины, слияния в точке; как узнать, где находится точка слияния?
Условия:
Если
следующим алгоритмом было бы решение.
Сначала цифры. Предположим, что первый список имеет длину a+c
, а второй имеет длину b+c
, где c
- длина их общего "хвоста" (после точки слияния). Обозначим их следующим образом:
x = a+c
y = b+c
Так как мы не знаем длину, мы будем вычислять x
и y
без дополнительных итераций; вы увидите, как.
Затем мы перебираем каждый список и реверсируем их во время итерации! Если оба итератора достигают точки слияния одновременно, то мы обнаруживаем это путем простого сравнения. В противном случае один указатель достигнет точки слияния перед другим.
После этого, когда другой итератор достигает точки слияния, он не переходит к общему хвосту. Вместо этого вернемся к прежнему началу списка, который раньше достиг точки слияния! Итак, до того, как он достигнет конца измененного списка (т.е. Первого начала другого списка), он выполнит a+b+1
итераций. Позвольте называть его z+1
.
Указатель, достигший первой точки слияния, будет продолжать итерацию до тех пор, пока не будет достигнут конец списка. Количество итераций, которые оно произвело, должно быть рассчитано и равно x
.
Затем этот указатель выполняет повторную повторную проверку и снова меняет списки. Но теперь он не вернется к началу списка, из которого он первоначально начинался! Вместо этого он перейдет к началу другого списка! Количество итераций, которые оно произвело, должно быть рассчитано и равно y
.
Итак, мы знаем следующие числа:
x = a+c
y = b+c
z = a+b
Из которого мы определяем, что
a = (+x-y+z)/2
b = (-x+y+z)/2
c = (+x+y-z)/2
Что решает проблему.
Следующее, безусловно, самое большое из всех, что я видел - O (N), никаких счетчиков. Я получил его во время интервью с кандидатом С.Н. на VisionMap.
Сделайте interating pointer следующим образом: он идет вперед каждый раз до конца, а затем переходит к началу противоположного списка и так далее. Создайте два из них, указывая на две головы. Продвигайте каждый указатель на 1 каждый раз, пока не встретитесь. Это произойдет после одного или двух проходов.
Я все еще использую этот вопрос в интервью, но посмотрю, сколько времени потребуется кому-то, чтобы понять, почему это решение работает.
Ответ Павла требует изменения списков, а также повторения каждого списка дважды.
Вот решение, которое требует только повторного повторения каждого списка дважды (первый раз, чтобы рассчитать их длину, если задана длина, вам нужно только один раз повторить).
Идея состоит в том, чтобы игнорировать стартовые записи более длинного списка (точка слияния не может быть там), так что два указателя равны расстоянию от конца списка. Затем переместите их вперед, пока они не сольются.
lenA = count(listA) //iterates list A
lenB = count(listB) //iterates list B
ptrA = listA
ptrB = listB
//now we adjust either ptrA or ptrB so that they are equally far from the end
while(lenA > lenB):
ptrA = ptrA->next
lenA--
while(lenB > lenA):
prtB = ptrB->next
lenB--
while(ptrA != NULL):
if (ptrA == ptrB):
return ptrA //found merge point
ptrA = ptrA->next
ptrB = ptrB->next
Это асимптотически то же (линейное время), что и мой другой ответ, но, вероятно, имеет меньшие константы, поэтому, вероятно, быстрее. Но я думаю, что мой другой ответ более холодный.
Хорошо, если вы знаете, что они сольются:
Предположим, вы начинаете с:
A-->B-->C
|
V
1-->2-->3-->4-->5
1) Пройдите первый список, указав каждый следующий указатель на NULL.
Теперь у вас есть:
A B C
1-->2-->3 4 5
2) Теперь перейдите во второй список и дождитесь появления NULL, то есть вашей точки слияния.
Если вы не можете быть уверены, что они слияния, вы можете использовать значение sentinel для значения указателя, но это не так элегантно.
Если бы мы могли перечислить списки ровно в два раза, я могу предоставить метод определения точки слияния:
Здесь решение, вычисляемое быстро (повторяет каждый список один раз), но использует много памяти:
for each item in list a
push pointer to item onto stack_a
for each item in list b
push pointer to item onto stack_b
while (stack_a top == stack_b top) // where top is the item to be popped next
pop stack_a
pop stack_b
// values at the top of each stack are the items prior to the merged item
Это, возможно, нарушает условие "разобрать каждый список только один раз", но реализует алгоритм черепахи и зайца (используется для поиска точки слияния и длина цикла циклического списка), поэтому вы начинаете с List A, и когда вы достигаете NULL в конце, вы притворяетесь, что это указатель на начало списка B, создавая таким образом появление циклического списка. Затем алгоритм скажет вам, как далеко вниз список A слияние (переменная "mu" в соответствии с описанием Википедии).
Кроме того, значение "лямбда" указывает длину списка B, и если вы хотите, вы можете определить длину списка A во время алгоритма (при перенаправлении ссылки NULL).
Вы можете использовать набор узлов. Перейдите через один список и добавьте каждый Node в набор. Затем перейдите во второй список и для каждой итерации проверьте, существует ли Node в наборе. Если это так, вы нашли свою точку слияния:)
Возможно, я более упрощаю это, но просто перебираю наименьший список и использую последние узлы Link
как точку слияния?
Итак, где Data->Link->Link == NULL
- конечная точка, дающая Data->Link
как точка слияния (в конце списка).
EDIT:
Хорошо, из фотографии, которую вы опубликовали, вы анализируете два списка, самые маленькие сначала. С наименьшим списком вы можете сохранить ссылки на следующие node. Теперь, когда вы разбираете второй список, вы делаете сравнение по ссылке, чтобы найти, где Reference [i] является ссылкой в LinkedList [i] → Link. Это даст точку слияния. Время для объяснения с картинками (накладывайте значения на изображение OP).
У вас есть связанный список (ссылки, показанные ниже):
A->B->C->D->E
У вас есть второй связанный список:
1->2->
В объединенном списке ссылки будут выглядеть следующим образом:
1->2->D->E->
Поэтому вы сопоставляете первый "меньший" список (поскольку объединенный список, который мы подсчитываем, имеет длину 4 и основной список 5)
Прокрутите первый список, сохраните ссылку ссылок.
Список будет содержать следующие ссылки Pointers { 1, 2, D, E }
.
Теперь перейдем ко второму списку:
-> A - Contains reference in Pointers? No, move on
-> B - Contains reference in Pointers? No, move on
-> C - Contains reference in Pointers? No, move on
-> D - Contains reference in Pointers? Yes, merge point found, break.
Конечно, вы сохраняете новый список указателей, но не вне спецификации. Однако первый список анализируется ровно один раз, а второй список будет полностью проанализирован, если нет точки слияния. В противном случае он скорее закончится (в точке слияния).
Я тестировал слияние на моем FC9 x86_64 и печатал каждый node адрес, как показано ниже:
Head A 0x7fffb2f3c4b0
0x214f010
0x214f030
0x214f050
0x214f070
0x214f090
0x214f0f0
0x214f110
0x214f130
0x214f150
0x214f170
Head B 0x7fffb2f3c4a0
0x214f0b0
0x214f0d0
0x214f0f0
0x214f110
0x214f130
0x214f150
0x214f170
Обратите внимание, что я выровнял структуру node, поэтому, когда malloc() a node, адрес выровнен по w/16 байтам, см. минимум 4 бита. Наименьшие биты равны 0, т.е. 0x0 или 000b. Поэтому, если вы находитесь в одном и том же специальном случае (выровненный node адрес), вы можете использовать эти минимум 4 бита. Например, при перемещении обоих списков с головы на хвост, установите 1 или 2 из 4 бит адреса посещения node, то есть установите флаг;
next_node = node->next;
node = (struct node*)((unsigned long)node | 0x1UL);
Примечание выше, флаги не будут влиять на реальный node адрес, но только ваше значение SAVED node.
Как только кто-то установил бит флага, первым найденным node должен быть точка слияния. после завершения, вы восстановите адрес node, очистив установленные биты флага. в то время как важно то, что вы должны быть осторожны при повторении (например, node= node → next) для очистки. помните, что вы установили бит флага, так что сделайте это
real_node = (struct node*)((unsigned long)node) & ~0x1UL);
real_node = real_node->next;
node = real_node;
Поскольку это предложение будет восстанавливать измененные адреса node, его можно рассматривать как "без изменений".
это решение выполняет итерацию каждого списка только один раз... никакая модификация списка не требуется слишком. Хотя вы можете жаловаться на космос..
1) В основном вы выполняете итерацию в списке1 и сохраняете адрес каждого node в массиве (который хранит значение unsigned int)
2) Затем вы перебираете list2, и для каждого node address --- > вы просматриваете массив, который вы находите совпадение или нет... если вы это сделаете, то это слияние node
//pseudocode
//for the first list
p1=list1;
unsigned int addr[];//to store addresses
i=0;
while(p1!=null){
addr[i]=&p1;
p1=p1->next;
}
int len=sizeof(addr)/sizeof(int);//calculates length of array addr
//for the second list
p2=list2;
while(p2!=null){
if(search(addr[],len,&p2)==1)//match found
{
//this is the merging node
return (p2);
}
p2=p2->next;
}
int search(addr,len,p2){
i=0;
while(i<len){
if(addr[i]==p2)
return 1;
i++;
}
return 0;
}
Надеюсь, это верное решение...
Нет необходимости изменять какой-либо список. Существует решение, при котором нам нужно пройти через каждый список только один раз.
Может быть простое решение, но потребуется дополнительное пространство. Идея состоит в том, чтобы просмотреть список и сохранить каждый адрес в хэш-карте, а затем перейти к другому списку и сопоставить, находится ли адрес в хэш-карте или нет. Каждый список просматривается только один раз. Там нет изменений в любом списке. Длина пока неизвестна. Используемое вспомогательное пространство: O (n), где 'n' - длина первого пройденного списка.
Вот наивное решение, No neeed для прохождения целых списков.
если ваш структурированный node имеет три поля, например
struct node {
int data;
int flag; //initially set the flag to zero for all nodes
struct node *next;
};
скажем, что у вас две головы (head1 и head2), указывающие на главу двух списков.
Пройдите оба списка с одинаковым темпом и поставьте флаг = 1 (флаг посещения) для этого node,
if (node->next->field==1)//possibly longer list will have this opportunity
//this will be your required node.
Как насчет этого:
Если вам разрешено перемещаться только по одному списку, вы можете создать новый node, пересечь первый список, чтобы каждый node указывал на этот новый node, и пересек второй список чтобы увидеть, указывает ли какой-либо node на ваш новый node (что ваша точка слияния). Если второй обход не приведет к вашему новому node, тогда исходные списки не будут иметь точку слияния.
Если вам разрешено перемещать списки более одного раза, вы можете перемещаться по каждому списку, чтобы найти свою длину, а если они разные, опустите "дополнительные" узлы в начале более длинного списка. Затем просто переходите оба списка по одному шагу за раз и найдите первое слияние node.
Шаги в Java:
Мы можем эффективно решить это, введя поле "isVisited". Перейдите в первый список и установите значение "isVisited" равным "true" для всех узлов до конца. Теперь начните со второго и найдите сначала node, где флаг true, и Boom, его точка слияния.
Шаг 1: найдите длину обоих списков Шаг 2. Найдите разницу и переместите самый большой список с разницей Шаг 3: Теперь оба списка будут в аналогичной позиции. Шаг 4: Итерируйте по списку, чтобы найти точку слияния
//Psuedocode
def findmergepoint(list1, list2):
lendiff = list1.length() > list2.length() : list1.length() - list2.length() ? list2.lenght()-list1.lenght()
biggerlist = list1.length() > list2.length() : list1 ? list2 # list with biggest length
smallerlist = list1.length() < list2.length() : list2 ? list1 # list with smallest length
# move the biggest length to the diff position to level both the list at the same position
for i in range(0,lendiff-1):
biggerlist = biggerlist.next
#Looped only once.
while ( biggerlist is not None and smallerlist is not None ):
if biggerlist == smallerlist :
return biggerlist #point of intersection
return None // No intersection found
int FindMergeNode(Node *headA, Node *headB)
{
Node *tempB=new Node;
tempB=headB;
while(headA->next!=NULL)
{
while(tempB->next!=NULL)
{
if(tempB==headA)
return tempB->data;
tempB=tempB->next;
}
headA=headA->next;
tempB=headB;
}
return headA->data;
}
Используйте Map или Dictionary, чтобы сохранить адресную строку и значение node. если альфа-адрес существует в Map/Dictionary, тогда значение ключа является ответом. Я сделал это:
int FindMergeNode(Node headA, Node headB) {
Map<Object, Integer> map = new HashMap<Object, Integer>();
while(headA != null || headB != null)
{
if(headA != null && map.containsKey(headA.next))
{
return map.get(headA.next);
}
if(headA != null && headA.next != null)
{
map.put(headA.next, headA.next.data);
headA = headA.next;
}
if(headB != null && map.containsKey(headB.next))
{
return map.get(headB.next);
}
if(headB != null && headB.next != null)
{
map.put(headB.next, headB.next.data);
headB = headB.next;
}
}
return 0;
}
AO (n) решение сложности. Но исходя из предположения.
Предположение таково: оба узла имеют только положительные целые числа.
Логика: сделать все целое число из списка1 отрицательным. Затем пройдитесь по list2, пока не получите отрицательное целое число. Найдя => возьми, поменяй знак обратно на положительный и вернись.
static int findMergeNode(SinglyLinkedListNode head1, SinglyLinkedListNode head2) {
SinglyLinkedListNode current = head1; //head1 is give to be not null.
//mark all head1 nodes as negative
while(true){
current.data = -current.data;
current = current.next;
if(current==null) break;
}
current=head2; //given as not null
while(true){
if(current.data<0) return -current.data;
current = current.next;
}
}
Это настоящая проблема! Если у нас есть древовидная структура в базе данных SQL. Мы хотим найти LCA в триггере SQL.
В среде триггера мы не можем создать временную таблицу, потому что она неявно фиксирует транзакцию. Таким образом, мы должны сделать это в памяти O (1), то есть мы можем использовать только переменную.