1つのパスでリンクリストの中間要素を見つける方法は?


12

データ構造とアルゴリズムから最も人気のある質問の1つで、主に電話インタビューで質問されました。


5
このスレッドで提示された答えがインタビュアーが期待したものである場合、この質問は技術的な能力をテストしませんが、候補者が弁護士のようにうまく回避できるかどうかをテストします。
G.バッハ

2
あいまいであいまいで主観的な「パス」という用語に大きく左右されるため、これはひどいインタビューの質問です。この質問に対するほぼすべての適切な答えは、定義を悪用して、事実上それを無視できるようにすることです。
RBarryYoung

2
さて、質問はここで多くの議論を引き起こします。それは、ある面で良い面接の質問であることを意味します:それはあなたが考え始めます。
ヘンドリック

3
「すべての要素をベクトルに読み込んでから、size()/ 2の位置にある要素にアクセスします」と答えたいのです。
Cort Ammon

1
@HendrikJan実際には、それは完全に恐ろしいインタビューの質問だと思います。第一に、生産的な議論ではなく、「ワンパスで」正確に何を意味するかについての議論につながる可能性があります。第二に、候補者は「正しい」答えを見つけ出し、それが「1パスで」の基準に違反すると考えたため、それを拒否することができました。第三に、それはよく知られている質問であるため、「一般的なインタビューの質問を知っていますか?」のより良いテストです。「あなたはこの仕事にぴったりですか?」それらのどれでも問題としてこれを沈めるのに十分なはずです。3つすべてが同時に災害になります。
デビッドリチャービー

回答:


24

不正行為と並行して2つのパスを同時に行うことによって。しかし、採用担当者がこれを好むかどうかはわかりません。

素敵なトリックを使用して、単一のリンクリストで実行できます。2つのポインターがリスト上を移動し、1つは倍速になります。速い方が終わりに達すると、もう片方は途中です。


1
ええ、これがシングルパスかどうかは不明です。その点に関する質問は不明確です。
デビッドリチャービー

2
ちなみに、これは2本のキャンドルがあり、それぞれが1時間燃えていて、45分を測定するように求められるパズルに関連しています。
ヘンドリック

2
これは、リストを反復し、要素をカウントし、中間点まで2回反復する場合と実際に異なるのですか?異なるのは、余分な半分を反復するときだけです。@RBarryYoungが他の同様の答えについて言及しているように、それは実際にはシングルパスではなく、パス半です。

2
リストが長い場合、両方のポインターを「並行して」移動すると、最初から2回繰り返すよりもキャッシュミスが少なくなります。
-zwol

2
これは、カメとウサギのアルゴリズムと同じ原理をサイクル検出に使用します。
ジョシュアテイラー

7

二重にリンクされたリストではない場合は、リストをカウントして使用するだけでかまいませんが、最悪の場合はメモリを2倍にする必要があり、リストが大きすぎてメモリに保存できない場合は機能しません。

単純な、ほとんどばかげた解決策は、2つのノードごとに中間ノードを増やすだけです。

function middle(start) {
    var middle = start
    var nextnode = start
    var do_increment = false;
    while (nextnode.next != null) {
        if (do_increment) {
             middle = middle.next;
        }
        do_increment = !do_increment;
        nextnode = nextnode.next;
    }
    return middle;
}

2番目のオプションは正解です(もちろん、IMHO)。
マシュークランリー

1
実際には、リンクされたリスト上で1 1/2パスを作成しています。
RBarryYoung

6

ヘンドリックの答えを詳しく述べる

二重リンクリストの場合は、両端から繰り返します

function middle(start, end) {
  do_advance_start = false;
  while(start !== end && start && end) {
     if (do_advance_start) {
        start = start.next
     }
     else {
        end = end.prev
     }
     do_advance_start = !do_advance_start
  }
  return (start === end) ? start : null;
}

与えられた [1, 2, 3] => 2

1, 3
1, 2
2, 2

与えられた [1, 2] => 1

1, 2
1, 1

与えられた [1] => 1

与えられた [] => null


これはどのように効率的ですか?また、n / 2ではなくn回繰り返します。
カランカンナ

3

リンクリストのノードを指すことができるポインターと、リスト内のノードの数のカウントを保持する整数変数を持つ構造体を作成します。

struct LL{
    struct node *ptr;
    int         count;
}start;

startptrstartcoあなたはnt=1
startcoあなたはntは、リンクリストに新しいノードが正常に作成された後、1ずつ増加します。同様に、ノードがリンクリストから削除されるたびに、1ずつ減分します。

startcoあなたはnt


-2

動的配列を作成します。配列の各要素は、リスト内の各ノードへのポインターであり、最初から順に走査されます。1に初期化された整数を作成し、訪問したノードの数を追跡します(新しいノードに行くたびに増加します)。最後に到達すると、リストの大きさがわかり、各ノードへのポインターの配列が整います。最後に、リストのサイズを2で割り(0ベースのインデックス付けの場合は1を減算)、配列のそのインデックスに保持されているポインターをフェッチします。リストのサイズが奇数の場合、返す要素を選択できます(最初の要素を返します)。

ここに、いくつかのJavaコードがあります(動的配列の概念は少し不安定ですが)。C / C ++を提供しますが、その分野では非常に錆びています。

public Node getMiddleNode(List<Node> nodes){

    int size = 1;
    //add code to dynamically increase size if at capacity after adding
    Node[] pointers = new Node[10];

    for (int i = 0; i < nodes.size(); i++){
        //remember to dynamically allocate more space if needed
        pointers[i] = nodes.get(i);
        size++;
    }

    return pointers[(size - 1)/2];

}

-3

再帰は複数のパスと見なされますか?

リストを最後まで走査し、参照により整数カウントを渡します。後で参照できるように、各レベルでその値のローカルコピーを作成し、次の呼び出しで参照カウントを増やします。

最後のノードで、カウントを2で除算し、結果を切り捨てます(2つの要素しかないときに最初のノードを「中間」にしたい場合)または切り上げます(2番目のノードにしたい場合)真ん中")。0または1ベースのインデックスを適切に使用します。

巻き戻し、参照カウントをローカルコピー(ノードの番号)に一致させます。等しい場合、そのノードを返します。それ以外の場合は、再帰呼び出しから返されたノードを返します。

これを行う方法は他にもあります。それらのいくつかはそれほど面倒ではないかもしれません(誰かがそれを配列に読み込み、配列の長さを使用して中間の賞賛を決定すると言う人を見たと思った)。しかし、率直に言って、それは愚かなインタビューの質問なので、良い答えはありません。ナンバーワン、まだリンクリストを使用している(支持意見)。2つ目は、真ん中のノードを見つけることは、現実のシナリオでは価値のないacademic意的な学術的課題です。3番目に、真ん中のノードを本当に知る必要がある場合、リンクリストはノードカウントを公開します。中間ノードが必要になるたびにリスト全体を走査する時間を無駄にするよりも、そのプロパティを維持する方が簡単です。そして最後に、4つ、すべてのインタビュアーが異なる答えを好むか拒否します-あるインタビュアーが滑らかだと思うもの、別のインタビュアーはばかげていると呼びます。

私はほとんどの場合、インタビューの質問にさらに質問をします。このような質問を受け取った場合(私は持っていない)、私は尋ねます; (2)私の制約は何ですか?メモリが問題にならない場合(配列の答えなど)は高速化できますが、インタビュアーがメモリの増加が無駄であると思う場合は、うんざりします。(3)どの言語で開発しますか?私が知っているほぼすべての現代言語には、リストの走査を不要にするリンクリストを処理する組み込みクラスがあります。言語の開発者によって効率が向上するように調整されたものを再発明するのはなぜですか。


6
誰が何をダウン票したのかわからないので、一人がすべてをダウン票したというあなたの結論は真実かもしれないし、そうでないかもしれないが、確かにあなたに利用可能な事実の根拠はない。しかし、コードの山ではなく説明を探しているので、私はあなたの答えを否定しています。
デビッドリチャービー

それは仮定かもしれませんが、私が一瞬を見て、30秒以内にすべての投稿が正確に-1になったとき、それは不合理なものではありません。たとえ5人または6人の異なる人々であったとしても、なぜコメントを残した人もいませんでした。しかし、少なくとも理由を述べてくれてありがとう。なぜ説明がコードよりも優れているのかわかりません-はい、電話インタビューのためです IEコードを持っているので投稿をダウンボットするとは思いませんが、少なくともその理由を言ってくれてありがとう。
ジェームズK

公正な点-投票のタイミングを知ることができたということは私にはありませんでした(投票中にページを読み込むことは、私たちのような普通のユーザーがそれを見つけることができる唯一の方法だと思います)。ここで実際のコードを避けようとする理由についてのメタ投稿がいくつかあります:(1) (2)
デビッドリチャービー

メタリンクをありがとう。私はよくある質問を読みましたが、そこには何も表示されませんでした-そのようなことに気づいたということではなく、おそらく、私が期待していたことではありませんでした。しかし、私はうんざりした後、再確認しても何も見つかりませんでした。私はまだ見落としているかもしれませんが、見ました。メタ投稿での推論は理にかなっています。応答いただきありがとうございます。
ジェームズK

-5

2つのポインターを使用する。反復ごとに1つずつ増加し、2番目の反復ごとに1ずつ増加します。1番目のポインターがリンクリストの終わりを指している場合、2番目のポインターはリンクリストの中央モードを指します。


これは、ヘンドリックの答えをそのまま複製したものです。何か新しいことを言うまでは答えないでください。
デビッドリチャービー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.