두 개의 개의 연결 목록이 병합 확인하십시오. 어디?
이 질문은 오래 대답 지 모르지만 대답을 생각할 수 없습니다.
길이가 다른 두 개의 목록이 한 지점에서 병합 됩니다. 어디에 어디에 어디에 있습니까?
정황 :
- 우리는 길이를
- 각 목록을 한 번만 구문 분석해야합니다.
만약
- "수정이 허용되지 않음"은 "변경할 수 있으며 결국 복원되어야 함"을 의미합니다.
- 목록을 정확히 두 번 반복 할 수 있습니다.
다음 알고리즘이 해결이 될 것입니다.
첫째, 숫자. 제리스트의 길이라고 가정 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 에서 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
이 틀림없이 "각 목록을 한 번만 구문 분석"조건을 위반 거북이와 토끼 알고리즘 (순환 목록의 병합 지점 및주기 길이를 찾는 데 사용됨)을 구현하여 목록 A에서 시작하고 목록에서 NULL에 도달하면 끝은 목록 B의 시작에 대한 포인터 인 척하여 순환 목록의 모양을 만듭니다. 그런 다음 알고리즘은 병합이 List A에서 얼마나 아래에 있는지 정확하게 알려줍니다 (Wikipedia 설명에 'mu').
또한 "람다"값은 목록 B의 길이를 알려줍니다. 원하는 경우 알고리즘 중에 목록 A의 길이를 계산할 수 있습니다 (NULL 링크를 리디렉션 할 때).
노드 세트를 사용할 수 있습니다. 하나의 목록을 반복하고 각 노드를 세트에 추가하십시오. 그런 다음 두 번째 목록을 반복하고 모든 반복에 대해 노드가 세트에 있는지 확인합니다. 여기서 병합 지점을 찾을 것입니다. :)
가장 작은 목록을 반복하고 마지막 노드 Link
를 병합 할 것입니다.
따라서 어디에 있습니까? (목록의 끝)으로 Data->Link->Link == NULL
제공 Data->Link
되는 끝 지점은 어디에 있습니까?
편집하다 :
좋아요, 게시 한 사진에서 두 목록을 가장 작은 목록부터 구문 분석합니다. 가장 작은 목록으로 다음 노드에 대한 참조를 있습니다. 이제 두 번째 목록을 구문 분석 할 때 참조를 비교하여 참조 [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에서 병합 케이스를 테스트하고 같이 모든 노드 주소를 인쇄했습니다.
Head A 0x7fffb2f3c4b0
0x214f010
0x214f030
0x214f050
0x214f070
0x214f090
0x214f0f0
0x214f110
0x214f130
0x214f150
0x214f170
Head B 0x7fffb2f3c4a0
0x214f0b0
0x214f0d0
0x214f0f0
0x214f110
0x214f130
0x214f150
0x214f170
노드 구조를 문장으로하고 malloc ()이 노드이면 주소가 16 바이트로 정렬 최소 4 비트를 참조하십시오. 최소 비트는 0, 즉 0x0 또는 000b입니다. 따라서 동일한 특수 사례 (정렬 된 노드 주소)에있는 경우에도 최소 4 비트를 사용할 수 있습니다. 예를 들어 두 목록을 머리에서 꼬리로 선택할 때 방문 노드 주소의 4 비트 중 1 또는 2를 설정합니다. 즉, 플래그를 설정합니다.
next_node = node->next;
node = (struct node*)((unsigned long)node | 0x1UL);
위의 플래그는 실제 노드 주소에 영향을주지 않고 SAVED 노드 포인터 값에만 영향을 미칩니다.
누군가 플래그 비트를 설정 한 것을 발견하면 처음 발견 된 노드가 병합 지점이어야합니다. 완료 한 후 설정 한 플래그 비트를 지워 노드 주소를 복원합니다. 중요한 것은 청소를 위해 반복 (예 : node = node-> next) 할 때주의해야한다는 것입니다. 플래그 비트를 설정했으면 기억하십시오.
real_node = (struct node*)((unsigned long)node) & ~0x1UL);
real_node = real_node->next;
node = real_node;
이 제안은 수정 된 노드 주소를 복원하기 때문에 "수정 없음"으로 될 수 있습니다.
이 솔루션은 각 목록을 한 번만 반복합니다 ... 목록 수정도 필요하지 않습니다. 공간에 대해 불평 할 수 있습니다.
1) 기본적으로 list1에서 반복하고 각 노드의 주소를 배열 (부호없는 정수 값을 저장하는)에 저장합니다.
2) 그런 다음 list2를 반복하고 각 노드의 주소에 대해 ---> 일치 항목을 찾거나 찾지 못한 배열을 검색합니다 ... 이것이 병합 노드입니다.
//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'은 순회 된 첫 번째 목록의 길이입니다.
목록을 수정할 필요가 없습니다. 각 목록을 한 번만 탐색하면되는 솔루션이 있습니다.
- 두 개의 스택을 생성합니다 (예 : stck1 및 stck2).
- 첫 번째 목록을 탐색하고 stck1에서 탐색하는 각 노드의 복사본을 푸시합니다.
- 2 단계와 동일하지만 이번에는 두 번째 목록을 탐색하고 stck2의 노드 사본을 푸시합니다.
- 이제 두 스택에서 팝하고 두 노드가 동일한 지 확인하고, 그렇다면 해당 노드에 대한 참조를 유지하십시오. 그렇지 않다면 동일한 이전 노드가 실제로 우리가 찾고 있던 병합 지점입니다.
여기 순진한 해결책이 있습니다. 전체 목록을 탐색 할 필요가 없습니다.
구조화 된 노드에 다음과 같은 세 개의 필드가있는 경우
struct node {
int data;
int flag; //initially set the flag to zero for all nodes
struct node *next;
};
두 목록의 헤드를 가리키는 두 개의 헤드 (head1 및 head2)가 있다고 가정합니다.
두 목록을 동일한 속도로 탐색하고 해당 노드에 대해 = 1 (visited flag) 플래그를 설정합니다.
if (node->next->field==1)//possibly longer list will have this opportunity
//this will be your required node.
이것은 어떤가요:
각 목록을 한 번만 탐색 할 수있는 경우 새 노드를 만들고 첫 번째 목록을 탐색하여 모든 노드가이 새 노드를 가리 키도록하고 두 번째 목록을 탐색하여 새 노드를 가리키는 노드가 있는지 확인할 수 있습니다 ( 그것이 당신의 병합 지점입니다). 두 번째 순회가 새 노드로 이어지지 않으면 원래 목록에 병합 지점이없는 것입니다.
목록을 두 번 이상 탐색 할 수있는 경우 각 목록을 탐색하여 길이를 찾을 수 있으며 다른 경우 더 긴 목록의 시작 부분에있는 "추가"노드를 생략하십시오. 그런 다음 두 목록을 한 번에 한 단계 씩 순회하고 첫 번째 병합 노드를 찾으십시오.
Java의 단계 :
- 지도를 만듭니다.
- 목록의 두 가지 분기에서 순회를 시작하고 노드 (예 : 노드 Id)와 관련된 고유 한 것을 사용하여 목록의 모든 순회 노드를 맵에 넣고 모두에 대한 시작에 값을 1로 입력합니다.
- 첫 번째 중복 키가 올 때마다 해당 키의 값을 증가시킵니다 (이제 값이> 1 인 2가되었다고 가정 해 보겠습니다.
- 값이 1보다 크고 두 목록이 병합되는 노드 여야하는 키를 가져옵니다.
"isVisited"필드를 도입하여 효율적으로 해결할 수 있습니다. 첫 번째 목록을 탐색하고 끝까지 모든 노드에 대해 "isVisited"값을 "true"로 설정합니다. 이제 두 번째에서 시작하여 flag가 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;
}
맵 또는 사전을 사용하여 노드의 주소 대 값을 저장하십시오. 지도 / 사전에 이미 주소가있는 경우 키의 값이 답입니다. 저는 이것을 했어요:
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) 복잡성 솔루션. 그러나 가정에 근거합니다.
가정 : 두 노드 모두 양의 정수만 가지고 있습니다.
논리 : list1의 모든 정수를 음수로 만듭니다. 그런 다음 음의 정수를 얻을 때까지 목록 2를 사용합니다. 일단 발견되면 => 가져 오기 가면 기호를 다시 양수로 변경하고 돌아갑니다.
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 데이터베이스에 트리 구조가있는 경우. SQL 트리거에서 LCA를 찾고 싶습니다.
트리거 환경에서는 암시 적으로 트랜잭션을 커밋하기 때문에 임시 테이블을 만들 수 없습니다. 그래서 우리는 O (1) 메모리에서해야합니다. 즉, 변수 만 사용할 수 있습니다.
참고 URL : https://stackoverflow.com/questions/1594061/check-if-two-linked-lists-merge-if-so-where
'IT' 카테고리의 다른 글
git diff 두 파일을 동일한 분기, 동일한 커밋 (0) | 2020.09.08 |
---|---|
이 프로그램은 어떻게 작동합니까? (0) | 2020.09.08 |
EditText, 외부 터치에 대한 명확한 증가 (0) | 2020.09.08 |
AppDelegate에서 UINavigationBar 배경색을 변경하는 방법 (0) | 2020.09.08 |
Python의 네트워크에 IP가 어디에 있는지 확인할 수 있습니까? (0) | 2020.09.08 |