[algorithm] 두 개의 연결 목록이 병합되는지 확인하십시오. 그렇다면 어디?

이 질문은 오래되었을 지 모르지만 대답을 생각할 수 없었습니다.

길이가 다른 두 개의 목록이 있으며 한 지점에서 병합됩니다 . 병합 지점이 어디에 있는지 어떻게 알 수 있습니까?

정황:

  1. 우리는 길이를 모릅니다
  2. 각 목록을 한 번만 구문 분석해야합니다.

두 개의 병합 된 연결 목록의 예.



답변

만약

  • “수정이 허용되지 않음”은 “변경할 수 있지만 결국 복원되어야 함”을 의미하며
  • 목록을 정확히 두 번 반복 할 수 있습니다.

다음 알고리즘이 해결책이 될 것입니다.

첫째, 숫자. 제리스트의 길이라고 가정 a+c하고, 두 번째의 길이이며 b+c, c(mergepoint 후) 공통의 “꼬리”의 길이이다. 다음과 같이 표시하겠습니다.

x = a+c
y = b+c

우리가 길이를 알 수 없기 때문에, 우리는 계산합니다 xy추가 반복없이; 당신은 방법을 볼 수 있습니다.

그런 다음 각 목록을 반복하고 반복하는 동안 되돌립니다! 두 반복자가 동시에 병합 지점에 도달하면 단순히 비교를 통해 알아냅니다. 그렇지 않으면 한 포인터가 다른 포인터보다 먼저 병합 지점에 도달합니다.

그 후 다른 반복기가 병합 지점에 도달하면 공통 꼬리로 진행되지 않습니다. 대신 이전에 병합 지점에 도달했던 목록의 이전 시작으로 돌아갑니다! 따라서 변경된 목록의 끝에 도달하기 전에 (즉, 다른 목록의 이전 시작 부분) 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 에서 SN 후보와의 인터뷰에서 얻었습니다 .

다음과 같이 인터 레이팅 포인터를 만듭니다. 매번 끝까지 앞으로 이동 한 다음 반대 목록의 시작 부분으로 점프합니다. 두 개의 머리를 가리키는 두 개를 만듭니다. 만날 때까지 각 포인터를 매번 1 씩 전진시킵니다. 이것은 하나 또는 두 번의 패스 후에 발생합니다.

저는 인터뷰에서 여전히이 질문을 사용합니다.하지만 누군가가이 솔루션이 작동하는 이유를 이해하는 데 얼마나 오래 걸리는지 확인합니다.


답변

Pavel의 대답은 목록 수정하고 각 목록을 두 번 반복해야합니다.

여기에 각 목록을 두 번만 반복해야하는 솔루션이 있습니다 (길이를 처음 계산할 때; 길이가 주어지면 한 번만 반복하면 됨).

아이디어는 더 긴 목록 (병합 지점이있을 수 없음)의 시작 항목을 무시하여 두 포인터가 목록 끝에서 같은 거리에 있도록하는 것입니다. 그런 다음 병합 될 때까지 앞으로 이동합니다.

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이 표시 될 때까지 기다립니다.

그들이 병합되는지 확신 할 수 없다면 포인터 값에 센티넬 값을 사용할 수 있지만 그렇게 우아하지는 않습니다.


답변

목록을 정확히 두 번 반복 할 수 있다면 병합 지점을 결정하는 방법을 제공 할 수 있습니다.

  • 두 목록을 반복하고 길이 A와 B를 계산합니다.
  • 길이 차이 계산 C = | AB |;
  • 두 목록을 동시에 반복하기 시작하지만 더 큰 목록에 추가 C 단계를 만듭니다.
  • 이 두 포인터는 병합 지점에서 서로 만날 것입니다.

답변

다음은 계산 속도가 빠르지 만 (각 목록을 한 번 반복) 많은 메모리를 사용하는 솔루션입니다.

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


답변

노드 세트를 사용할 수 있습니다. 하나의 목록을 반복하고 각 노드를 세트에 추가하십시오. 그런 다음 두 번째 목록을 반복하고 모든 반복에 대해 노드가 세트에 있는지 확인합니다. 그렇다면 병합 지점을 찾은 것입니다. 🙂