web-dev-qa-db-ja.com

単一リンクリストの最後からn番目の要素を見つける方法

次の関数は、単一リンクリストのnth to last要素を見つけようとしています。

例えば:

要素が8->10->5->7->2->1->5->4->10->10の場合、結果は7thになり、最後のノードは7になります。

このコードがどのように機能するかについて誰かが私を助けることができますか、より良い、より簡単なアプローチがありますか?

LinkedListNode nthToLast(LinkedListNode head, int n) {
  if (head == null || n < 1) {
    return null;
  }

  LinkedListNode p1 = head;
  LinkedListNode p2 = head;

  for (int j = 0; j < n - 1; ++j) { // skip n-1 steps ahead
    if (p2 == null) {
      return null; // not found since list size < n
    }
    p2 = p2.next;
  }

  while (p2.next != null) {
    p1 = p1.next;
    p2 = p2.next;
  }

  return p1;
}
59
Jony

アルゴリズムは、N個のノードが離れているリンクリスト内の2つのノードへの参照を最初に作成することで機能します。したがって、あなたの例では、Nが7の場合、p1を8に、p2を4に設定します。

次に、p2がリストの最後の要素に到達するまで、各ノード参照をリスト内の次のノードに進めます。繰り返しになりますが、これはp1が5でp2が10の場合です。この時点で、p1はリストの最後からN番目の要素を参照しています(Nノード離れているというプロパティにより)。

35
Eric

このアルゴリズムの鍵は、最初に_p1_ノードで_p2_と_n-1_の2つのポインターを設定して、_p2_が_(n-1)th_ノードを指すようにすることですリストの先頭から、リストのlastノードに到達するまで_p2_を移動します。 _p2_がリストの最後に到達すると、_p1_はリストの最後からn番目のノードを指します。

説明をコメントとしてインラインに入れました。それが役に立てば幸い:

_// Function to return the nth node from the end of a linked list.
// Takes the head pointer to the list and n as input
// Returns the nth node from the end if one exists else returns NULL.
LinkedListNode nthToLast(LinkedListNode head, int n) {
  // If list does not exist or if there are no elements in the list,return NULL
  if (head == null || n < 1) {
    return null;
  }

  // make pointers p1 and p2 point to the start of the list.
  LinkedListNode p1 = head;
  LinkedListNode p2 = head;

  // The key to this algorithm is to set p1 and p2 apart by n-1 nodes initially
  // so we want p2 to point to the (n-1)th node from the start of the list
  // then we move p2 till it reaches the last node of the list. 
  // Once p2 reaches end of the list p1 will be pointing to the nth node 
  // from the end of the list.

  // loop to move p2.
  for (int j = 0; j < n - 1; ++j) { 
   // while moving p2 check if it becomes NULL, that is if it reaches the end
   // of the list. That would mean the list has less than n nodes, so its not 
   // possible to find nth from last, so return NULL.
   if (p2 == null) {
       return null; 
   }
   // move p2 forward.
   p2 = p2.next;
  }

  // at this point p2 is (n-1) nodes ahead of p1. Now keep moving both forward
  // till p2 reaches the last node in the list.
  while (p2.next != null) {
    p1 = p1.next;
    p2 = p2.next;
  }

   // at this point p2 has reached the last node in the list and p1 will be
   // pointing to the nth node from the last..so return it.
   return p1;
 }
_

あるいは、_p1_と_p2_を_(n-1)_の代わりにnノード離れて設定し、最後のノードまで移動する代わりにリストの最後まで_p2_を移動できます。

_LinkedListNode p1 = head;
LinkedListNode p2 = head;
for (int j = 0; j < n ; ++j) { // make then n nodes apart.
    if (p2 == null) {
        return null;
    }
    p2 = p2.next;
}
while (p2 != null) { // move till p2 goes past the end of the list.
    p1 = p1.next;
    p2 = p2.next;
}
return p1;
_
66
codaddict

このアプローチについてどう思いますか。

  1. Linkedlistの長さをカウントします。
  2. 実際のNode先頭からのインデックス= linkedlistの長さ-指定されたインデックス。
  3. 頭からtravesreに関数を記述し、上記のインデックスでノードを取得します。
10
Pritam Karmakar
//this  is the recursive solution


//initial call
find(HEAD,k);

// main function
void find(struct link *temp,int k)
{  
 if( temp->next != NULL)
   find( temp->next, k);
 if((c++) == k)       // c is initially declared as 1 and k is the node to find from last.
  cout<<temp->num<<' ';
}
7
dekontj

すでに多くの回答がありますが、それらはすべてリストを2回(連続的または並行して)歩いたり、大量の追加ストレージを使用したりします。

一定の余分なスペースを使用して、リストを1回(および少し)歩きながらこれを行うことができます。

Node *getNthFromEnd(Node *list, int n) {

    if (list == null || n<1) {
        return null; //no such element
    }

    Node *mark1 = list, *mark2 = list, *markend = list;
    int pos1 = 0, pos2 = 0, posend = 0;

    while (markend!=null) {
        if ((posend-pos2)>=(n-1)) {
            mark1=mark2;
            pos1=pos2;
            mark2=markend;
            pos2=posend;
        }
        markend=markend->next;
        ++posend;
    }
    if (posend<n) {
        return null; //not enough elements in the list
    }

    //mark1 and mark2 are n-1 elements apart, and the end is at least
    //1 element after mark2, so mark1 is at least n elements from the end

    while((posend - pos1) > n) {
      mark1 = mark1->next;
      ++pos1;
    }
    return mark1;
}

このバージョンでは、N+nトラバーサルより少ない2つの追加ポインターを使用します。Nはリストの長さで、nは引数です。

M余分なポインターを使用する場合、N+ceil(n/(M-1))にそれを取得できます(そして、それらを循環バッファーに保存する必要があります)

3
Matt Timmermans

これは宿題のように聞こえるので、実際の解決策を提供するのではなく、あなたが自分で手助けできるようにしたいと思います。

いくつかの小さなサンプルデータセットでこのコードを実行することをお勧めします。デバッガを使用して、ステップごとに行を実行します(関数の開始時にブレークポイントを設定できます)。これにより、コードがどのように機能するかがわかります。

Console.WriteLine()を使用して、目的の変数を出力することもできます。

2
mafu

この問題の別の解決策です。時間の複雑さは変わりませんが、このコードは単一ループでソリューションを実現します。

public Link findKthElementFromEnd(MyLinkedList linkedList, int k)
    {
        Link current = linkedList.getFirst();//current node
        Link currentK = linkedList.getFirst();//node at index k

        int counter = 0;

        while(current.getNext()!=null)
        {
            counter++;

            if(counter>=k)
            {
                currentK = currentK.getNext();
            }

            current = current.getNext();
        }

        //reached end
        return currentK;
    }
2
sunsin1985

Linkedlistをループして、サイズを取得できます。サイズがわかれば、2nのn番目の項を見つけることができます。これはO(n)まだです。

public T nthToLast(int n) {
    // return null if linkedlist is empty
    if (head == null) return null;

    // declare placeholder where size of linkedlist will be stored
    // we are hoping that size of linkedlist is less than MAX of INT
    int size = 0;

    // This is O(n) for sure
    Node i = head;
    while (i.next != null) {
        size += 1;
        i = i.next;
    }

    // if user chose something outside the size of the linkedlist return null
    if (size < n)
        return null;

    // This is O(n) if n == size
    i = head;
    while(size > n) {
        size--;
        i = i.next;
    }

    // Time complexity = n + n = 2n
    // therefore O(n)

    return i.value;
}
2
Y_Y

C#のソリューション。ダミー値でLinkedListを作成します。

  LinkedList<int> ll = new LinkedList<int>();
            ll.AddFirst(10);
            ll.AddLast(12);
            ll.AddLast(2);
            ll.AddLast(8);
            ll.AddLast(9);
            ll.AddLast(22);
            ll.AddLast(17);
            ll.AddLast(19);
            ll.AddLast(20);

最初のノードを指す2つのポインターp1&p1を作成します。

        private static bool ReturnKthElement(LinkedList<int> ll, int k)
        {
            LinkedListNode<int> p1 = ll.First;
            LinkedListNode<int> p2 = ll.First;

いずれかのp2がヌルになるまでループを繰り返します。つまり、linkedlistの長さがK番目の要素OR

            for (int i = 0; i < k; i++)
            {
                p2 = p2.Next;
                if (p2 == null)
                {
                    Console.WriteLine($"Linkedlist is smaller than {k}th Element");
                    return false;
                }
            }

ここで、p2がヌルになるまで両方のポインターを繰り返します。 p1ポインターに含まれる値はK番目の要素に対応します


            while (p2 != null)
            {
                p1 = p1.Next;
                p2 = p2.Next;
            }
            //p1 is the Kth Element
            Console.WriteLine($"Kth element is {p1.Value}");
            return true;
        }
2
VAT

リンクされたリストを線形時間で反転し、k番目の要素を見つけるだけです。それでも線形時間で実行されます。

2
Nithin
public int nthFromLast(int n){
    Node current = head;
    Node reference = head;      
    for(int i=0;i<n;i++){
        reference=reference.getNext();
    }
    while(reference != null){
        current = current.getNext();
        reference = reference.getNext();
    }
    return current.getData();
}
1
kaila88

2つのポインターpTempとNthNodeを使用します。最初は、両方ともリストのヘッドノードを指します。 NthNodeは、pTempがn回移動した後にのみ移動を開始します。両方から、pTempがリストの最後に達するまで前進します。その結果、NthNodeはリンクリストの最後からn番目のノードを指します。

public ListNode NthNodeFromEnd(int n){
    ListNode pTemp = head, NthNode = null;
   for(int count=1; count<n;count++){
     if(pTemp!=null){
       pTemp = pTemp.getNext();
     }
   }
   while(pTemp!=null){
     if(NthNode==null){
         NthNode = head;
     }
     else{
        NthNode = NthNode.getNext();
     }
     pTemp = pTemp.getNext();
   }
   if(NthNode!=null){
     NthNode = NthNode.getNext();
     return NthNode;
   }
return null;
}

TextBookを参照してください:「Javaで簡単にできるデータ構造とアルゴリズム」

1
Balasubramanian

この問題を理解するには、測定例との簡単なアナロジーを行う必要があります。たとえば、中指から正確に1メートル離れた腕の場所を見つけなければならない場合、どのように測定しますか?長さ1メートルの定規をつかみ、その定規の上端を中指の先端に置くと、メーターの下端が中指の上端からちょうど1メートル離れます。指。

この例で行うことは同じです。n要素幅のフレームが必要です。フレームをリストの最後に配置するだけで、フレームの開始ノードは正確にn-リストの最後までの要素。

これは、リストにM個の要素があり、N個の要素の幅があるフレームを想定したリストです。

HEAD -> EL(1) -> EL(2) -> ... -> EL(M-1) -> EL(M)

<-- Frame -->

ただし、必要なのはフレームの境界だけです。したがって、フレームの終了境界は、フレームの開始境界から正確に(N-1)要素離れます。したがって、これらの境界要素のみを保存する必要があります。それらをAおよびBと呼びましょう。

HEAD -> EL(1) -> EL(2) -> ... -> EL(M-1) -> EL(M)

A <- N-Element Wide-> B

最初にしなければならないことは、フレームの終わりであるBを見つけることです。

ListNode<T> b = head;
int count = 1;

while(count < n && b != null) {
    b = b.next;
    count++;
}

ここで、bは配列のn番目の要素であり、a[〜#〜] head [〜#〜]にあります。フレームが設定されたので、bがリストの最後に到達するまで、両方の境界ノードをステップごとにインクリメントしますaはn-th-toになります-最後の要素;

ListNode<T> a = head;

while(b.next != null) {
    a = a.next;
    b = b.next;
}

return a;

すべてをまとめて、HEADチェック、N <M(Mはリストのサイズ))チェックおよびその他のもので、完全な解決方法を示します。

public ListNode<T> findNthToLast(int n) {
    if(head == null) {
        return null;
    } else {
        ListNode<T> b = head;
        int count = 1;

        while(count < n && b != null) {
            b = b.next;
            count++;
        }

        if(count == n && b!=null) {
            ListNode<T> a = head;

            while(b.next != null) {
                a = a.next;
                b = b.next;
            }

            return a;
        } else {
            System.out.print("N(" + n + ") must be equal or smaller then the size of the list");
            return null;
        }
    }
}
1

StackOverflowの別のスレッドで再帰ソリューションを使用しています here

1
sanjay

リンクリストの長さはわかりません... Likedlistの長さを取得するために1回通過する必要があるため、アプローチはほとんど効率的ではありません。

1
CodeR

ここでは、2つのポインターpNodeとqNodeを使用します。両方の初期ポイントはqNodeを指します。次に、リストの最後までトラバースします。カウントと位置の差が0より大きく、pthNodeが各ループで1回増加する場合にのみ、pNodeがトラバースします。

static ListNode nthNode(int pos){
ListNode pNode=head;
ListNode qNode=head;
int count =0;
while(qNode!=null){
    count++;
    if(count - pos > 0)
        pNode=pNode.next;
    qNode=qNode.next;
}
return pNode;
}
1
Rohit

キャリアカップの本で与えられた問題はわずかに異なります。単方向リンクリストのn番目から最後の要素を見つけるという。

ここに私のコードがあります:

    public void findntolast(int index)
    {
        Node ptr = front; int count = 0;
        while(ptr!=null)
        {
            count++;
            if (count == index)
            {
                front = ptr;
                break;
            }
            ptr = ptr.next;
        }
        Node temp=front;
        while(temp!=null)
        {
            Console.WriteLine(temp.data);
            temp=temp.next;
        }
    }
0
Akshay

再帰的ソリューション:

Node findKth (Node head, int count, int k) {
    if(head == null)
        return head;
    else {
        Node n =findKth(head.next,count,k);
        count++;

        if(count == k)
            return head;

        return n;
    }
}
0
Maher Rezeq

私のアプローチ、私が考えるものはシンプルで、時間の複雑さはO(n)です。

ステップ1:最初にノード数のカウントを取得します。最初のノードから最後のノードまでforループを実行します

ステップ2:カウントを取得したら、単純な数学を適用します。たとえば、7番目のノードを最後のノードに見つけて、すべてのノードのカウントが12である場合、(count-index)-1はk番目のノードを与えます。横断する必要があり、最後のノードからn番目のノードになります。この場合(12 -7)-1 = 4

要素が8-> 10-> 5-> 7-> 2-> 1-> 5-> 4-> 10-> 10の場合、結果は7番目から最後のノードが7で、これは4番目のノードにすぎません始まり。

0
Dhananjaya HS

追加のデータ構造を使用できます..そう簡単な場合...すべてのノードをスタックにプッシュし始め、カウンターを維持してポップします。あなたの例に従って、8-> 10-> 5-> 7-> 2-> 1-> 5-> 4-> 10-> 10リンクリストの読み取りを開始し、ノードまたはノード->データのプッシュを開始しますスタック。スタックは上-> {10、10,4、5、1、2、7、5、10、8} <-下のようになります。

スタックの最上部からカウンタを1に維持してポップを開始し、ポップするたびに、n番目の要素(この例では7番目の要素)に達すると、カウンタを1ずつ増やします。

注:これは、データ/ノードを逆の順序で印刷または取得します

0
funnyCoder

2ポインターアプローチを使用したコードは次のとおりです:( source

低速で高速なポインターアプローチ

struct node
{
  int data;
  struct node *next;
}mynode;


mynode * nthNodeFrmEnd(mynode *head, int n /*pass 0 for last node*/)
{
  mynode *ptr1,*ptr2;
  int count;

  if(!head)
  {
    return(NULL);
  }

  ptr1  = head;
  ptr2  = head;
  count = 0;

  while(count < n)
  {
     count++;
     if((ptr1=ptr1->next)==NULL)
     {
        //Length of the linked list less than n. Error.
        return(NULL);
     }
  }

  while((ptr1=ptr1->next)!=NULL)
  {
    ptr2=ptr2->next;
  }

  return(ptr2);
}


再帰

node* findNthNode (node* head, int find, int& found){
    if(!head) {
        found = 1;
        return 0;
    }
    node* retval = findNthNode(head->next, find, found);
    if(found==find)
        retval = head;
    found = found + 1;
    return retval;
}

0
kinshuk4

Linklistからn番目の子を見つけるC#バージョンです。

public Node GetNthLast(Node head, int n)
    {
        Node current, nth;
        current = nth = head;
        int counter = 0;

        while (current.next != null)
        {
            counter++;
            if (counter % n == 0)
            {
                for (var i = 0; i < n - 1; i++)
                {
                    nth = nth.next;
                }
            }
            current = current.next;
        }
        var remainingCounts = counter % n;
        for (var i = 0; i < remainingCounts; i++)
        {
            nth = nth.next;
        }
        return nth;
    }
0
Shafqat Ali

ハッシュテーブルを使用して上記の問題を解決することもできます。ハッシュテーブルのエントリは、ノードの位置とノードのアドレスです。したがって、最後からn番目のノードを見つけたい場合(これは最初からm-n + 1を意味し、mはノードの数です)、ハッシュテーブルエントリを入力すると、ノードの数が得られます。

1.各ノードをトラバースし、ハッシュテーブルに対応するエントリを作成します。

2.ハッシュテーブルでm-n + 1ノードを探して、アドレスを取得します。

時間の複雑さはO(n)です。

0
Shiv Shakti

メモリコストの許容値(このソリューションではO(k))に応じて、長さkのポインターの配列を割り当て、リンクリストを走査しながらノードに循環配列として格納できます。

リンクされたリストの走査が終了すると、配列の最初の要素(循環配列であるため、必ず0インデックスを適切に計算するようにしてください)で答えが得られます。

配列の最初の要素がnullの場合、問題の解決策はありません。

0

私は質問コードに1つの欠陥があると思います、そして、それが本からどのようにこれが可能かについて疑問に思います...それは正しく実行されるかもしれませんが、コードはいくらか論理的に間違っています。 forループ内... if条件をp2->next ! = NULLに対してチェックする必要があります

 for (int j = 0; j < n - 1; ++j) { // skip n-1 steps ahead
       if (p2->next == null) {
       return null; // not found since list size < n
   }

...残りは問題なく、すでにコードシフトp2(n-1)の位置がp1に進んでいるので、説明中にwhileループでp2->nextが終了するまで同時に移動します..あなたが私の答えが間違っていると思ったら教えてくれました

0
user1107108

まず第一に

コメントで言及したように、より明確にするために、質問はfrom

<Cracking the coding interview 6th> | IX Interview Questions | 2. Linked Lists | Question 2.2

GoogleのソフトウェアエンジニアGayle Laakmann McDowellによる素晴らしい本で、多くの人にインタビューしています。


アプローチ

(リンクリストが長さを追跡しないと仮定)O(n) timeには2つのアプローチがあり、およびO(1)スペース:

  • 最初に長さを見つけ、次に(len-k + 1)要素にループします。
    私が覚えているように、この解決策は本で言及されていません。
  • 2ポインターを介してループし、それらを(k-1)の距離に保ちます。
    この解決策は、質問と同じように、本からのものです。

コード

以下は、単体テストを使用したJavaの実装です(JDK自体の高度なデータ構造を使用せずに)

KthToEnd.Java

/**
 * Find k-th element to end of singly linked list, whose size unknown,
 * <p>1-th is the last, 2-th is the one before last,
 *
 * @author eric
 * @date 1/21/19 4:41 PM
 */
public class KthToEnd {
    /**
     * Find the k-th to end element, by find length first.
     *
     * @param head
     * @param k
     * @return
     */
    public static Integer kthToEndViaLen(LinkedListNode<Integer> head, int k) {
        int len = head.getCount(); // find length,

        if (len < k) return null; // not enough element,

        return (Integer) head.getKth(len - k).value; // get target element with its position calculated,
    }

    /**
     * Find the k-th to end element, via 2 pinter that has (k-1) distance.
     *
     * @param head
     * @param k
     * @return
     */
    public static Integer kthToEndVia2Pointer(LinkedListNode<Integer> head, int k) {
        LinkedListNode<Integer> p0 = head; // begin at 0-th element,
        LinkedListNode<Integer> p1 = head.getKth(k - 1); // begin at (k-1)-th element,

        while (p1.next != null) {
            p0 = p0.next;
            p1 = p1.next;
        }

        return p0.value;
    }

    static class LinkedListNode<T> {
        private T value;
        private LinkedListNode next;

        public LinkedListNode(T value) {
            this.value = value;
        }

        /**
         * Append a new node to end.
         *
         * @param value
         * @return new node
         */
        public LinkedListNode append(T value) {
            LinkedListNode end = getEnd();
            end.next = new LinkedListNode(value);
            return end.next;
        }

        /**
         * Append a range of number, range [start, end).
         *
         * @param start included,
         * @param end   excluded,
         */
        public void appendRangeNum(Integer start, Integer end) {
            KthToEnd.LinkedListNode last = getEnd();
            for (int i = start; i < end; i++) {
                last = last.append(i);
            }
        }

        /**
         * Get end element of the linked list this node belongs to, time complexity: O(n).
         *
         * @return
         */
        public LinkedListNode getEnd() {
            LinkedListNode end = this;
            while (end != null && end.next != null) {
                end = end.next;
            }

            return end;
        }

        /**
         * Count of element, with this as head of linked list.
         *
         * @return
         */
        public int getCount() {
            LinkedListNode end = this;
            int count = 0;
            while (end != null) {
                count++;
                end = end.next;
            }

            return count;
        }

        /**
         * Get k-th element from beginning, k start from 0.
         *
         * @param k
         * @return
         */
        public LinkedListNode getKth(int k) {
            LinkedListNode<T> target = this;
            while (k-- > 0) {
                target = target.next;
            }

            return target;
        }
    }
}

KthToEndTest.Java

(単体テスト、TestNGを使用、または必要に応じてJUnit/..に変更)

import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

/**
 * KthToEnd test.
 *
 * @author eric
 * @date 1/21/19 5:20 PM
 */
public class KthToEndTest {
    private int len = 10;
    private KthToEnd.LinkedListNode<Integer> head;

    @BeforeClass
    public void prepare() {
        // prepare linked list with value [0, len-1],
        head = new KthToEnd.LinkedListNode(0);
        head.appendRangeNum(1, len);
    }

    @Test
    public void testKthToEndViaLen() {
        // validate
        for (int i = 1; i <= len; i++) {
            Assert.assertEquals(KthToEnd.kthToEndViaLen(head, i).intValue(), len - i);
        }
    }

    @Test
    public void testKthToEndVia2Pointer() {
        // validate
        for (int i = 1; i <= len; i++) {
            Assert.assertEquals(KthToEnd.kthToEndVia2Pointer(head, i).intValue(), len - i);
        }
    }
}

ヒント:

  • KthToEnd.LinkedListNode
    これはゼロから実装された単純な単一リンクリストノードであり、それ自体から始まるリンクリストを表します。
    ヘッド/テール/長さを追加で追跡しませんが、それを行う方法があります。
0
Eric Wang

In Java私は使用します-

public class LL {
  Node head;
  int linksCount;

   LL(){
     head = new Node();
     linksCount = 0;
   }

  //TRAVERSE TO INDEX
  public Node getNodeAt(int index){
    Node temp= head;
    if(index > linksCount){
        System.out.println("index out of bound !");
        return null;
    }
    for(int i=0;i<index && (temp.getNext() != null);i++){
        temp = temp.getNext();
    }
    return temp.getNext();
  }
}
0
Manisha

NがLinkedListの長さよりも大きい場合、JonathanのバージョンがNullPinterExceptionをスローすることに誰も気付きませんでした。これが私のバージョンです:

public Node nth(int n){
        if(head == null || n < 1) return null;

        Node n1 = head;
        Node n2 = head;
        for(int i = 1; i < n; i++){
            if(n1.next == null) return null; 
            n1 = n1.next;
        }

        while (n1.next != null){
            n1 = n1.next;
            n2 = n2.next;
        }
        return n2;
}

ここで少し変更を加えるだけです。ノードn1が前進すると、n1がnullであるかどうかを確認する代わりに、n1.nextがnullであるかどうかを確認します。

0
sofia