blog

アルゴリズム・レビュー:環状連鎖表

アイデア:連鎖表がループしているかどうかを判断するためにハッシュ表を使ってノードが訪問されたかどうかを検出します。これが最も簡単な解決策です。単純すぎて、直接コードに: 私たちが使用したJSの()メソ...

Oct 15, 2020 · 5 min. read
シェア

今日、私たちはあなたをもたらす、リングにチェーンテーブル検出の古典的なトピック。あなたがそう思うなら、あなたはより多くの忍耐強く、慎重に読みたいと思うかもしれませんしてください、私はいくつかの異なる収穫があると信じています!またはトピックYOで始まる、準備はいいですか? 行こう!

この本では、トピックを分析しています。

連鎖表が与えられたとき、その中に環があるかどうかを判定しなさい。与えられた連鎖表にリングがあることを表すには、連鎖表の中で端のリンクがつながっている位置を表す整数posを使います。pos が -1 なら、その連鎖表にはリングがありません。

例1:

入力:頭 = [3,2,0,-4], pos = 1
出力:真
解説:連鎖表の中にリングがあり、そのテールは2番目のノードに接続されている。

例2:

入力:頭 = [1,2], pos = 0
出力:真
解説:連鎖表の中に、末尾が最初のノードにつながっているリングがある。

例 3:

入力:頭 = [1], pos = -1
出力:偽
解説:連鎖表には輪がない。

このタイトルは簡単すぎると思うかもしれません!でも、辛抱強く読んでみてください!

そうすれば必ず何かを得られるはずです!

この本では、トピックを分析しています。

トピック解決策1:ハッシュテーブルの決定



アイデア:チェーンテーブルがリング状になっているかどうかを判断するために、ノードがハッシュテーブルを通してアクセスされたかどうかを検出します。これが一番考えやすい。コードに直行するにはシンプルすぎます:

func hasCycle(head *ListNode) bool {
 m := make(map[*ListNode]int)
 for head != nil {
 if _,exist := m[head];exist {
 return true
 }
 m[head]= 1 9
 head = head.Next
 }
 return false12
}


問題解決2:JS特殊解



JSのJSON.stringify()メソッドは、主にJSオブジェクトをJSON文字列に変換するために使用します。基本的な使い方は以下の通りです:

var car = { 
 name: ' , 
 age: 20, 
} 
var str = JSON.stringify(car);
console.log(str) 
//=> {"name":" ,"age":20}

考えてみてください、このような関数を自分で実装する場合、どのような特殊なケースに対応する必要があるでしょうか?そう、循環参照です。というのも、循環参照はJSONの構造で示すのが難しいからです!例えば、以下のような場合です:

 var a = {} 
 var b = { 
 a: a 
 }
 a.b = b
 console.log(JSON.stringify(a))
 //=> TypeError: Converting circular structure to JSON

環状に連結されたテーブルは、環状構造ではありませんか?もちろんそうです!なぜなら、循環連鎖テーブルである限り、以下のようなコードを持たなければならないからです:

a.Next = b b.Next = a

つまり、JSON.stringify()のプロパティで解決できます:

var hasCycle = function(head) {
 try{
 JSON.stringify(head)
 }catch(e){
 return true
 }
 return false
};

もちろん、この解決策は、この問題に対する標準的な解決策ではありません!もちろん、この解決策は、この問題に対する標準的な解決策ではありません!



問題解決法3:ダブルポインタの解決法



この問題の標準的な解法です!マスターすべき常識的な要素です!



アイデアソース:まず、2人のアスリートが異なるスピードでトラックを走ったときに何が起こるかを想像してください。ミーティング!さて、こんな質問が出ます。



解答:速度の異なる2つのポインタ(速いポインタと遅いポインタ)を使って連鎖表をトラバースすることで、空間の複雑さをO(1)に減らすことができます。遅いポインタは一度に1ステップ移動し、速いポインタは一度に2ステップ移動します。

連鎖表を

と仮定すると、ステップは以下のようになります:

分析が終わったら、そのままコードに進みます:

func hasCycle(head *ListNode) bool { 
 if head == nil {
 return false
 }
 fast := head.Next // クイック・ポインター、一度に2つのステップ
 for fast != nil && head != nil && fast.Next != nil {
 if fast == head { // 速いポインタと遅いポインタが出会い、リングを示す
 return true
 }
 fast = fast.Next.Next 
 head = head.Next // スロー・ポインタ、一歩ずつ
 }
 return false
}

スロー・ポインタのステップを1に、ファースト・ポインタのステップを2に設定する理由は重要です。



まず、スロー・ポインターのステップ数が1である理由は簡単で、スロー・ポインターを各要素まで歩かせる必要があるからです。2の高速ポインタのステップの長さは、1つだけの相対速度の違いの共通点として理解することができ、高速のみ1つのグリッドで、必然的にグリッドで会う遅い追いかけることができます。



もし理解できないなら、分析する:速い速い遅い追いつくために、それらの間に1~2マスだけでなければなりません。もし1マス遅れていれば、次に追いつくでしょう。もし2マス遅れていれば、次は1マス遅れて、次に追いつきます!下の図のように:

つまり、高速ポインターのステップサイズは2に設定できます。

特記事項

いわゆる「素朴な疑問」にしばしば遭遇し、前世代が残した「古典的な解法」を使って素早く回答することがあります。問題を解く過程で、定型化、テンプレート化を追求するのです。もちろん、社会でも仕事でも勉強でも必要なことですから、このプロセスは良いことです!しかし、私は、それが最適な解決策でなくても、それは私自身のアイデア、創造であり、私自身の思考の一部を残すことができることを願っています!アルゴリズミックな問題に喜びを感じてください。

私が書いたすべての解答を、各問題の完全な図解付きの電子書籍に整理しました。

Read next