インタビューで「リンクリストのループを検出する方法は?」という質問を受けましたが、解決しましたが、インタビュアーからリンクリストのループを削除する方法を尋ねられました。私は手探りした。
これを解決する方法についてのポインタは、疑似コード、またはメソッド定義かもしれませんか?
Javaに満足しているので、Javaでこの質問にタグを付けました。
インスタンスの場合、このリンクリストにはループがあります
0--->1---->2---->3---->4---->5---->6
▲ |
| ▼
11<—-22<—-12<—-9<—-8
この問題には2つの部分があります。
ループの開始位置がわかれば、リストの最後の要素は簡単に特定できます。これは、ループの開始に続く最後の要素がループの開始を指すことになるためです。次に、この要素の次のポインタ/参照をnull
に設定して、循環リンクリスト(最後の要素が最初を指す循環リンクリストではない)を修正するのは簡単です。これは特定のインスタンスです。循環リストの)。
カメとウサギのアルゴリズムとも呼ばれるフロイドのサイクル検出アルゴリズム 異なる速度で移動する2つのポインター/参照を使用するため、サイクルを検出する1つの方法です。サイクルがある場合、2つのポインタ(p1
とp2
など)は、有限数のステップの後で同じ要素を指すことになります。興味深いことに、それらが出会う要素はloopの開始点と同じ距離であることが証明できます(-===-)(ループの開始はリストのheadに向かうため、同じ順方向にリストをトラバースし続けます。つまり、リストの線形部分にk
要素がある場合、2つのポインターは、ループの開始点m-k
で長さm
のループ内で出会うか、 k
要素をループの「終了」に追加します(もちろん、これはループであるため、「終了」はありません。もう一度「開始」にすぎません)。これにより、ループの開始点を見つけることができます。
サイクルが検出されたら、p2
が上記のステップのループが終了した要素をポイントしたままにしますが、p1
をリセットして、リストの先頭を指すようにします。次に、各ポインタを一度に1要素ずつ移動します。 p2
はループ内で開始されたため、ループを継続します。 k
ステップ(ループの開始からリストの先頭までの距離に等しい)の後、p1
とp2
が再び出会います。これは、ループの開始への参照を提供します。
ループを開始する要素を指すようにp1
(またはp2
)を設定し、p1
が開始要素を指すようになるまでループをトラバースすることが簡単になりました。この時点で、p1
は「最後の」要素リストを参照しており、その次のポインターはnull
に設定できます。
ここにいくつかの迅速で汚いJavaコードがNode
sのリンクされたリストを想定し、ここでNode
がnext
参照を持っています。これは最適化できますが、それはあなたに基本的な考えを与えるはずです:
Node slow, fast, start;
fast = slow = head;
//PART I - Detect if a loop exists
while (true)
{
// fast will always fall off the end of the list if it is linear
if (fast == null || fast.next == null)
{
// no loop
return;
}
else if (fast == slow || fast.next == slow)
{
// detected a loop
break;
}
else
{
fast = fast.next.next; // move 2 nodes at at time
slow = slow.next; // move 1 node at a time
}
}
//PART II - Identify the node that is the start of the loop
fast = head; //reset one of the references to head of list
//until both the references are one short of the common element which is the start of the loop
while(fast.next != slow.next)
{
fast = fast.next;
slow = slow.next;
}
start = fast.next;
//PART III - Eliminate the loop by setting the 'next' pointer
//of the last element to null
fast = start;
while(fast.next != start)
{
fast = fast.next;
}
fast.next = null; //break the loop
この説明 はパートIIの背後にある理由を助けるかもしれません:
サイクルの長さがMで、リンクリストの残りの長さがLであると仮定します。t1/ t2が最初に出会ったときのサイクルの位置を理解しましょう。
サイクルの最初のノードが位置0であると定義します。位置1、2、...、M-1までのリンクをたどります。 (サイクルを歩くとき、現在の位置は(walk_length)mod Mですよね?)t1/t2が最初に位置pで出会うと仮定し、次にそれらの移動時間は同じです(L + k1 * M + p)/ v =(L + k2 * M + p)/ 2v、一部のk1
したがって、t1がpから始まり、t2が先頭から始まり、同じ速度で移動する場合、サイクルの最初のノードである位置0で会うことを認めます。 QED。
その他の参照:
ソリューション1-提供: キャリアカップおよび「コーディングインタビュー」の本 :
public static LinkedListNode findStartOfLoop(LinkedListNode head) {
LinkedListNode n1 = head;
LinkedListNode n2 = head;
// find meeting point using Tortoise and Hare algorithm
// this is just Floyd's cycle detection algorithm
while (n2.next != null) {
n1 = n1.next;
n2 = n2.next.next;
if (n1 == n2) {
break;
}
}
// Error check - there is no meeting point, and therefore no loop
if (n2.next == null) {
return null;
}
/* Move n1 to Head. Keep n2 at Meeting Point. Each are k steps
/* from the Loop Start. If they move at the same pace, they must
* meet at Loop Start. */
n1 = head;
while (n1 != n2) {
n1 = n1.next;
n2 = n2.next;
}
// Now n2 points to the start of the loop.
return n2;
}
このソリューションの説明は本から直接です:
2つのポインターを移動する場合、1つは速度1、もう1つは速度2で、リンクリストにループがある場合、それらは最終的に一致します。どうして?トラックを運転している2台の車について考えてみましょう。速い車は常に遅い車を追い越します!
ここでトリッキーな部分は、ループの開始点を見つけることです。アナロジーとして、2人の人がトラックの周りをレースしていて、一方が他方の2倍の速さで走っていると想像してください。同じ場所から始めた場合、次に会うのはいつですか。彼らは次のラップの開始時に次に会います。
ここで、Fast Runnerがnステップラップでkメートルのヘッドスタートをしたとします。次に会うのはいつですか?彼らは次のラップの開始前にkメートルに会います。 (なぜですか?Fast Runnerはヘッドスタートを含めてk + 2(n-k)ステップを作成し、Slow Runnerはn-kステップを作成します。どちらもループの開始前にkステップになります)。
ここで問題に戻ります。FastRunner(n2)とSlow Runner(n1)が循環リンクリストの周りを移動しているとき、n2はn1に入ったときにループの先頭から始まります。具体的には、kの先頭から始まります。ここで、kはループ前のノード数です。 n2にはk個のノードの先頭の開始があるため、n1とn2はループの開始前にk個のノードに出会います。
したがって、次のことがわかりました。
- ヘッドはLoopStartからのkノードです(定義により)
- N1およびn2のMeetingPointは、LoopStartからのkノードです(上記を参照)。
したがって、n1をHeadに戻し、n2をMeetingPointに保持し、両方を同じペースで移動すると、LoopStartで出会います。
ソリューション2-提供:)
public static LinkedListNode findHeadOfLoop(LinkedListNode head) {
int indexer = 0;
Map<LinkedListNode, Integer> map = new IdentityHashMap<LinkedListNode, Integer>();
map.put(head, indexer);
indexer++;
// start walking along the list while putting each node in the HashMap
// if we come to a node that is already in the list,
// then that node is the start of the cycle
LinkedListNode curr = head;
while (curr != null) {
if (map.containsKey(curr.next)) {
curr = curr.next;
break;
}
curr = curr.next;
map.put(curr, indexer);
indexer++;
}
return curr;
}
これがお役に立てば幸いです。
フリスト
この応答は、答えを競うためのものではなく、亀とうさぎアルゴリズムの2つのノードの出会いについてもう少し説明するためのものです。
両方のノードが最終的にループに入ります。一方が他方よりも速く(F)移動するため(S)、(F)は最終的にラップ(S)します。
ループの開始がリストの先頭にある場合、(F)はリストの先頭で(S)に戻る必要があります。これは、(F)の速度が2X(S)の速度だからです。これが3倍の場合、これは当てはまりません。これは、(S)が1周したときに(F)が1周するため、(S)が1周目を完了すると、(F)が2周したため、(S)でループの最初に戻るためです。
ループの開始がリストの先頭にない場合、(S)がループに入るまでに、(F)はループ内の(k)ノードの先頭を持っています。 (S)の速度は一度に1つのノードのみであるため、ループの開始から(k)のノードで(F)に出会います-(k)開始に到達する前にさらにステップがあり、(k)ステップが後ではありません開始。 (S)の速度が1でなく、速度比が(F)と(S)の間で2:1でない場合、これは当てはまりません。
3.1。ここで、説明が少し難しくなります。 (F)が最終的に(上記1を参照)を満たすまで(S)をラッピングし続けることに同意できますが、なぜ(k)でループの開始からのノードですか?次の方程式を考えます。ここで、Mはノードの数またはループの距離であり、kはヘッドスタート(F)です。方程式は、左側の時間tが与えられたときに(F)が移動した距離を、右側の(S)が移動した距離で表します。
d_F(t)= 2 * d_S(t)+ k
したがって、(S)がループに入り、ループ内を0距離移動した場合、(F)は(k)距離だけ移動しました。時間までにd_S = M-k、d_F = 2M-k。 Mがループ内の1周の合計距離を表すことを考慮してモジュラー数学も使用する必要があるため、M全体(残りなし)での(F)と(S)の位置は0です。 POSITION(または微分)、これはk(または、むしろ-k)を残します。
そして最後に、(S)と(F)は、ループの開始点から(0-k)、または(k)ノードの位置で出会います。
上記の[3]の場合、(k)はヘッドスタート(F)の2倍の移動距離(S)がリストの先頭からループに入るまでの移動距離(S)を表すため、(k)はリストの開始からの距離。これは、ループの開始を表します。
ここは少し遅いので、効果的に説明できたと思います。それ以外の場合はお知らせください。返信を更新します。
アイデンティティハッシュマップ( IdentityHashMap など)の使用が許可されている場合、これは非常に簡単に解決できます。ただし、より多くのスペースを使用します。
ノードリストをトラバースします。検出されたノードごとに、それをアイデンティティマップに追加します。ノードがすでにアイデンティティマップに存在する場合、リストには循環リンクがあり、この競合の前にあったノードがわかります(移動する前にチェックするか、「最後のノード」を保持します)。サイクルを断ち切る。
この単純なアプローチに従うことは楽しい練習になるはずです。コードは読者のための練習として残されます。
ハッピーコーディング。
0--->1---->2---->3---->4---->5---->6
▲ |
| ▼
11<—-22<—-12<—-9<—-8
リンクリストのすべてのノードの後にダミーノードを挿入し、挿入する前に、次のノードがダミーかどうかを確認します。 next to nextがダミーの場合、そのノードの隣にnullを挿入します。
0-->D->1-->D->2-->D->3->D-->4->D-->5->D-->6
▲ |
/ ▼
11<—D<-22<—D<-12<—D<-9<—D<--8
Node(11)->next->next == D
Node(11)->next =null
//Find a Loop in Linked List and remove link between node
public void findLoopInList() {
Node fastNode = head;
Node slowNode = head;
boolean isLoopExist = false;
while (slowNode != null && fastNode != null && fastNode.next != null) {
fastNode = fastNode.next.next;
slowNode = slowNode.next;
if (slowNode == fastNode) {
System.out.print("\n Loop Found");
isLoopExist = true;
break;
}
}
if (isLoopExist) {
slowNode = head;
Node prevNode = null;
while (slowNode != fastNode) {
prevNode = fastNode;
fastNode = fastNode.next;
slowNode = slowNode.next;
}
System.out.print("Loop Found Node : " + slowNode.mData);
prevNode.next = null; //Remove the Loop
}
}
:)GlbMP