Проверьте, сливаются ли два связанных списка. Если да, то где?

Этот вопрос может быть старым, но я не мог придумать ответа.

Скажем, есть два списка разной длины, слияния в точке; как узнать, где находится точка слияния?

Условия:

  • Мы не знаем длину
  • Мы должны анализировать каждый список только один раз.

Example of two merged linked lists.

Ответ 1

Если

  • "модификация не допускается", это означало "вы можете изменить, но в конце концов их нужно восстановить" и
  • мы могли бы перебирать списки ровно дважды

следующим алгоритмом было бы решение.

Сначала цифры. Предположим, что первый список имеет длину 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

Что решает проблему.

Ответ 2

Следующее, безусловно, самое большое из всех, что я видел - O (N), никаких счетчиков. Я получил его во время интервью с кандидатом С.Н. на VisionMap.

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

Я все еще использую этот вопрос в интервью, но посмотрю, сколько времени потребуется кому-то, чтобы понять, почему это решение работает.

Ответ 3

Ответ Павла требует изменения списков, а также повторения каждого списка дважды.

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

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

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

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

Ответ 4

Хорошо, если вы знаете, что они сольются:

Предположим, вы начинаете с:

A-->B-->C
        |
        V
1-->2-->3-->4-->5

1) Пройдите первый список, указав каждый следующий указатель на NULL.

Теперь у вас есть:

A   B   C

1-->2-->3   4   5

2) Теперь перейдите во второй список и дождитесь появления NULL, то есть вашей точки слияния.

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

Ответ 5

Если бы мы могли перечислить списки ровно в два раза, я могу предоставить метод определения точки слияния:

  • итерация обоих списков и вычисление длин A и B
  • вычислить разность длин C = | A-B |;
  • запустить итерирование обоих списков одновременно, но сделать дополнительные C-шаги в списке, которые были больше
  • эти два указателя будут встречаться друг с другом в точке слияния

Ответ 6

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

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

Ответ 7

Это, возможно, нарушает условие "разобрать каждый список только один раз", но реализует алгоритм черепахи и зайца (используется для поиска точки слияния и длина цикла циклического списка), поэтому вы начинаете с List A, и когда вы достигаете NULL в конце, вы притворяетесь, что это указатель на начало списка B, создавая таким образом появление циклического списка. Затем алгоритм скажет вам, как далеко вниз список A слияние (переменная "mu" в соответствии с описанием Википедии).

Кроме того, значение "лямбда" указывает длину списка B, и если вы хотите, вы можете определить длину списка A во время алгоритма (при перенаправлении ссылки NULL).

Ответ 8

Вы можете использовать набор узлов. Перейдите через один список и добавьте каждый Node в набор. Затем перейдите во второй список и для каждой итерации проверьте, существует ли Node в наборе. Если это так, вы нашли свою точку слияния:)

Ответ 9

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

Конечно, вы сохраняете новый список указателей, но не вне спецификации. Однако первый список анализируется ровно один раз, а второй список будет полностью проанализирован, если нет точки слияния. В противном случае он скорее закончится (в точке слияния).

Ответ 10

Я тестировал слияние на моем 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, его можно рассматривать как "без изменений".

Ответ 11

это решение выполняет итерацию каждого списка только один раз... никакая модификация списка не требуется слишком. Хотя вы можете жаловаться на космос..
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;
} 

Надеюсь, это верное решение...

Ответ 12

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

  1. Создайте два стека, скажем, stck1 и stck2.
  2. Пройдите по 1-му списку и нажмите копию каждого узла, который вы проходите в stck1.
  3. То же, что во втором шаге, но на этот раз переходим во 2-й список и отправляем копию узлов в stck2.
  4. Теперь извлеките из обоих стеков и проверьте, равны ли два узла, если да, то сохраните ссылку на них. Если нет, то предыдущие узлы, которые были равны, фактически являются точкой слияния, которую мы искали.

Ответ 13

Может быть простое решение, но потребуется дополнительное пространство. Идея состоит в том, чтобы просмотреть список и сохранить каждый адрес в хэш-карте, а затем перейти к другому списку и сопоставить, находится ли адрес в хэш-карте или нет. Каждый список просматривается только один раз. Там нет изменений в любом списке. Длина пока неизвестна. Используемое вспомогательное пространство: O (n), где 'n' - длина первого пройденного списка.

Ответ 14

Вот наивное решение, 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. 

Ответ 15

Как насчет этого:

  • Если вам разрешено перемещаться только по одному списку, вы можете создать новый node, пересечь первый список, чтобы каждый node указывал на этот новый node, и пересек второй список чтобы увидеть, указывает ли какой-либо node на ваш новый node (что ваша точка слияния). Если второй обход не приведет к вашему новому node, тогда исходные списки не будут иметь точку слияния.

  • Если вам разрешено перемещать списки более одного раза, вы можете перемещаться по каждому списку, чтобы найти свою длину, а если они разные, опустите "дополнительные" узлы в начале более длинного списка. Затем просто переходите оба списка по одному шагу за раз и найдите первое слияние node.

Ответ 16

Шаги в Java:

  • Создайте карту.
  • Начните перемещение в обеих ветвях списка и поместите все пройденные узлы списка в карту, используя некоторую уникальную вещь, связанную с узлами (например, node Id) в качестве ключа и поместите значения как 1 в начало для всех.
  • Когда появляется первый дублирующий ключ, увеличьте значение для этого ключа (скажем, теперь его значение стало равным 2, которое равно 1.
  • Получить ключ, где значение больше 1, и это должно быть node, где сгруппированы два списка.

Ответ 17

Мы можем эффективно решить это, введя поле "isVisited". Перейдите в первый список и установите значение "isVisited" равным "true" для всех узлов до конца. Теперь начните со второго и найдите сначала node, где флаг true, и Boom, его точка слияния.

Ответ 18

Шаг 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

Ответ 19

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

Ответ 20

Используйте 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;
}

Ответ 21

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

}

Ответ 22

Это настоящая проблема! Если у нас есть древовидная структура в базе данных SQL. Мы хотим найти LCA в триггере SQL.

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