この質問は古いかもしれませんが、答えを考えることができませんでした。
たとえば、長さの異なる2つのリスト、が1つのポイントでマージされる;どのようにして合流点がどこにあるかを知ることができますか?
条件:
もし
次のアルゴリズムが解決策になります。
まず、数字。最初のリストの長さはa+c
で、2番目のリストの長さはb+c
であると想定します。ここで、c
は(マージポイントの後の)一般的な「テール」の長さです。それらを次のように示しましょう。
x = a+c
y = b+c
長さがわからないため、追加の反復なしでx
およびy
を計算します。方法がわかります。
次に、各リストを繰り返し、繰り返しながらそれらを逆にします!両方のイテレータが同時にマージポイントに到達した場合、単純に比較することでそれを見つけます。そうしないと、1つのポインターが他のポインターの前にマージポイントに到達します。
その後、他のイテレータがマージポイントに到達すると、共通のテールには進みません。代わりに、以前にマージポイントに到達したリストの前の先頭に戻ります!そのため、変更されたリストの最後(つまり、他のリストの前の部分)に到達する前に、彼は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)、カウンターなし。 S.N.候補者へのインタビューでそれを得た VisionMap で。
このように相互作用するポインターを作成します。最後まで毎回進み、反対のリストの先頭にジャンプします。 2つの頭を指すこれらの2つを作成します。ポインタが出会うまで、毎回1ずつ進めます。これは、1つまたは2つのパスの後に発生します。
私はまだインタビューでこの質問を使用していますが、誰かがこのソリューションが機能する理由を理解するのにどれくらい時間がかかるかを見るために。
Pavelの答えは、リストの修正を必要とします同様に各リストを2回繰り返す。
onlyで各リストを2回反復する必要があるソリューションを示します(最初にその長さを計算する。長さが指定されている場合、1回だけ反復する必要があります)。
これは、2つのポインターがリストの末尾から等しい距離になるように、長いリストの開始エントリを無視することです(マージポイントは存在できません)。次に、結合するまでそれらを前方に移動します。
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)2番目のリストを調べて、NULL(マージポイント)が表示されるまで待ちます。
確実にマージできない場合は、ポインタ値にセンチネル値を使用できますが、それほどエレガントではありません。
リストを正確に2回反復できる場合、マージポイントを決定する方法を提供できます。
これは、計算が速い(各リストを1回繰り返す)ソリューションですが、大量のメモリを使用します。
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
これはおそらく「各リストを1回だけ解析する」条件に違反しますが、 カメとウサギのアルゴリズム (循環リストのマージポイントとサイクル長を見つけるために使用)を実装し、リストAから開始し、最後にNULLに到達すると、リストBの先頭へのポインタであると偽装し、循環リストの外観を作成します。アルゴリズムは、リストAのマージがどれだけ下にあるかを正確に示します(Wikipediaの説明によると、変数「mu」)。
また、「ラムダ」値はリストBの長さを示し、必要に応じて、アルゴリズム中(NULLリンクをリダイレクトするとき)にリストAの長さを計算できます。
一連のノードを使用できます。 1つのリストを反復処理し、各Node=をセットに追加します。2番目のリストを反復処理し、反復ごとに、Nodeがセットに存在するかどうかを確認します。もしそうなら、あなたはあなたのマージポイントを見つけました:)
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;
この提案は変更されたノードアドレスを復元するため、「変更なし」と見なすことができます。
たぶん私はこれを単純化しすぎているかもしれませんが、単に最小のリストを繰り返し、最後のノードLink
をマージポイントとして使用しますか?
だから、Data->Link->Link == NULL
は終点であり、Data->Link
をマージポイントとして(リストの最後に)。
編集:
さて、投稿した写真から、2つのリストを解析します。最小のものが最初です。最小のリストを使用すると、次のノードへの参照を維持できます。ここで、2番目のリストを解析するとき、参照で比較を行い、参照[i]がLinkedList [i]-> Linkの参照である場所を見つけます。これにより、マージポイントが得られます。写真で説明する時間(写真に値を重ね合わせますOP)。
リンクされたリストがあります(以下に示す参照)。
A->B->C->D->E
2番目のリンクリストがあります。
1->2->
マージされたリストを使用すると、参照は次のようになります。
1->2->D->E->
したがって、最初の「より小さい」リストをマップします(マージリストの長さは4で、メインリストは5です)
最初のリストをループし、参照の参照を維持します。
リストには、次の参照が含まれますPointers { 1, 2, D, E }
。
次に、2番目のリストを確認します。
-> 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.
もちろん、ポインタの新しいリストを維持しますが、それは仕様の範囲外ではありません。ただし、最初のリストは1回だけ解析され、2番目のリストはマージポイントがない場合のみ完全に解析されます。それ以外の場合は、(マージポイントで)より早く終了します。
簡単な解決策もありますが、補助スペースが必要になります。これは、リストを走査して各アドレスをハッシュマップに保存し、もう一方のリストを走査して、アドレスがハッシュマップにあるかどうかを照合するという考え方です。各リストは1回だけ走査されます。リストに変更はありません。長さはまだ不明です。使用される補助スペース:O(n)ここで、「n」は最初にトラバースされたリストの長さです。
このソリューションは、各リストを1回だけ反復します...リストの変更も必要ありません。ただし、スペースについて文句を言うかもしれません。
1)基本的にはlist1を反復処理し、各ノードのアドレスを配列(unsigned int値を格納する)に格納します
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;
}
それが有効な解決策であることを願っています...
リストを変更する必要はありません。各リストを一度だけトラバースするだけで済む解決策があります。
ここに素朴な解決策があります。リスト全体を横断する必要はありません。
構造化ノードに次のような3つのフィールドがある場合
struct node {
int data;
int flag; //initially set the flag to zero for all nodes
struct node *next;
};
2つのリストの頭を指す2つの頭(head1とhead2)があるとします。
同じペースで両方のリストをトラバースし、そのノードにフラグ= 1(訪問済みフラグ)を設定し、
if (node->next->field==1)//possibly longer list will have this opportunity
//this will be your required 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;
}
これはどう:
各リストを1回だけトラバースできる場合、新しいノードを作成し、最初のリストをトラバースしてすべてのノードがこの新しいノードを指すようにし、2番目のリストをトラバースして、ノードが新しいノードを指しているかどうかを確認できます(それがあなたのマージポイントです)。 2番目のトラバースが新しいノードにつながらない場合、元のリストにはマージポイントがありません。
リストを複数回トラバースすることが許可されている場合、各リストをトラバースして長さを確認できます。異なる場合は、長いリストの先頭にある「余分な」ノードを省略します。次に、両方のリストを一度に1ステップずつトラバースし、最初のマージノードを見つけます。
ステップ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
Javaでの手順:
これは本当の問題です! SQLデータベースにツリー構造がある場合。 SQLトリガーでLCAを見つけたい。
トリガー環境では、トランザクションを暗黙的にコミットするため、一時テーブルを作成できません。したがって、O(1)メモリで行う必要があります。つまり、変数のみを使用できます。
A O(n)複雑さのソリューション。ただし、仮定に基づいています。
仮定は、両方のノードが正の整数のみを持っていることです。
ロジック:list1のすべての整数を負にします。次に、負の整数が得られるまで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;
}
}
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;
}
「isVisited」フィールドを導入することにより、効率的に解決できます。最初のリストを走査し、最後まですべてのノードの「isVisited」値を「true」に設定します。次に、2番目から開始して、フラグがtrueで、Boomがマージポイントである最初のノードを見つけます。