如何从单链表的末尾找到第n个元素?

时间:2010-04-08 08:03:57

标签: algorithm linked-list data-structures

以下函数正在尝试查找单链表的nth最后元素。

例如:

如果元素是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;
}

29 个答案:

答案 0 :(得分:64)

此算法的关键是最初将p1p2两个指针设置为n-1个节点,因此我们希望p2指向(n-1)th从列表开头的节点然后我们移动p2直到它到达列表的last节点。 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;
 }

或者我们可以将p1p2分开n个节点而不是(n-1),然后移动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;

答案 1 :(得分:35)

您的算法的工作原理是首先创建对链接列表中两个节点分开的节点的引用。因此,在您的示例中,如果N为7,则将p1设置为8,将p2设置为4.

然后它会将每个节点引用前进到列表中的下一个节点,直到p2到达列表中的最后一个元素。同样,在您的示例中,这将是当p1为5且p2为10.此时,p1指的是列表中最后一个元素的第N个(由属性表示它们是N个节点分开)。

答案 2 :(得分:10)

您对此方法有何看法。

  1. 计算链接列表的长度。
  2. 来自head = linkedlist长度的实际节点索引 - 给定索引;
  3. 从头开始写一个函数到travesre并获取上述索引的节点。

答案 3 :(得分:7)

//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<<' ';
}

答案 4 :(得分:3)

这里已经有很多答案了,但它们都会在列表中走两次(顺序或并行)或者使用大量的额外存储空间。

你可以使用恒定的额外空间在列表中行走一次(加上一点点):

Public Sub Selection()

Set WorkBook1 = Workbooks.Open(TextBox2.Text).Sheets(1)
Set WorkBook2 = Workbooks.Open(TextBox5.Text).Sheets(1)

        lngLastRow = WorkBook1 .Range("A" & WorkBook1 .Rows.Count).End(xlUp).Row

For Index = 2 To lngLastRow   

    Dim varFacility As Variant
    Dim facilityRng As Range

    On Error Resume Next

        lngLastRow = WorkBook1 .Range("A" & WorkBook1 .Rows.Count).End(xlUp).Row
        Set facilityRng = WorkBook1 .Range("A1:A" & lngLastRow )

        varFacility = WorkBook1 .Range("A" & rownum).Value
        varPosition = Application.WorksheetFunction.Match(varFacility, facilityRng, 0)

    If Err = 0 Then

        WorkBook1 .Range("A" & rownum).Value = WorkBook2 .Range("B" & varPosition).Value

        If WorkBook2 .Range("C" & rownum).Value Like "91%-100% utilization*" Then

           WorkBook2 .Range("D" & rownum).Value = Selected
           WorkBook2 .Range("E" & rownum).Value = For Checking

    End If
   End If

End Sub

此版本使用2个额外指针,其次数少于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是列表的长度,N是参数。

如果使用n个额外指针,可以将其降低到M(并且应该将它们存储在循环缓冲区中)

答案 5 :(得分:2)

这个问题的另一个解决方案。虽然时间复杂度保持不变,但此代码在单个循环中实现了解决方案。

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;
    }

答案 6 :(得分:2)

您可以循环浏览链接列表并获取大小。一旦你有了这个大小,就可以在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;
}

答案 7 :(得分:2)

只需在线性时间内反转链表,找到第k个元素。它仍然以线性时间运行。

答案 8 :(得分:2)

由于这听起来像是家庭作业,我更愿意帮助你自己,而不是提供实际的解决方案。

我建议您在一些小样本数据集上运行此代码。使用调试器逐步运行行(您可以在函数的开头设置断点)。这应该可以让您了解代码的工作原理。

您还可以Console.WriteLine()输出感兴趣的变量。

答案 9 :(得分:1)

不,你不知道链表的长度...... 你必须经历一次才能得到喜欢列表的长度,这样你的方法效率就会很低;

答案 10 :(得分:1)

我们在这里拿两个指针pNode和qNode,这两个指针都是头部qNode。然后,遍历到列表的末尾,只有当计数和位置之间的差值大于0且pthNode在每个循环中递增一次时,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;
}

答案 11 :(得分:1)

我在StackOverflow here

中的另一个线程上有我的递归解决方案

答案 12 :(得分:1)

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();
}

答案 13 :(得分:1)

使用两个指针pTemp和NthNode。最初,两者都指向列表的头节点。只有在pTemp进行了n次移动后,NthNode才开始移动。从两者向前移动直到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简化数据结构和算法”

答案 14 :(得分:1)

要理解这个问题,我们应该用一个测量例子做一个简单的比喻。让我们说,你必须找到你手臂的位置,距你的中指正好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个到最后一个元素;

ListNode<T> a = head;

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

return a;

为了收集所有东西,并且通过HEAD检查,N&lt; 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;
        }
    }
}

答案 15 :(得分:0)

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为空-这意味着链表长度小于Kth元素,或者直到Kth元素为止

            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;
        }

答案 16 :(得分:0)


首先

正如评论中提到的那样,但更明确地说,问题来自

  

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

这是Google的一位软件工程师Gayle Laakmann McDowell写的一本好书,采访了很多人。


方法

(假设链表不跟踪长度) O(n)时间有两种方法,而 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
    这是一个从头开始实现的简单的单链列表节点,它表示从自身开始的链列表。
    尽管它具有执行此操作的方法,但它不会另外跟踪头/尾/长度。

答案 17 :(得分:0)

我只是借助我在操作(插入/删除)期间维护的“大小”变量来处理场景。

   public int GetKthFromTheEnd(int node)
    {
        var sizeIndex = size; //  mantained the list size 
        var currentNode = first;
        while (sizeIndex-- >0)
        {
            if ((node - 1) == sizeIndex)
                return currentNode.value;

            currentNode = currentNode.next;
        }

        throw new ArgumentNullException();
    }

答案 18 :(得分:0)

根据内存成本容差(此解决方案中的O(k)),我们可以分配长度为k的指针数组,并在遍历链表时将节点填充为圆形数组。

当我们遍历链表时,数组的第一个元素(只是确保正确计算0指数,因为它是一个圆形数组),我们将得到答案。

如果数组的第一个元素为null,则无法解决我们的问题。

答案 19 :(得分:0)

这是从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;
    }

答案 20 :(得分:0)

这里没有人注意到如果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是否为空时,我检查天气n1.next为空,否则在while循环中n1.next将抛出NullPinterException。

答案 21 :(得分:0)

在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();
  }
}

答案 22 :(得分:0)

我的方法,我认为很简单,时间复杂度为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个节点。

答案 23 :(得分:0)

以下是使用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;
}

答案 24 :(得分:0)

你可以使用额外的数据结构..如果是这样,它将很简单...开始将所有节点推送到堆栈,维护计数器弹出它。根据您的示例,8-> 10-> 5-> 7-> 2-> 1-> 5-> 4-> 10-> 10开始读取链表并开始推送节点或节点 - >数据到堆栈上。所以堆栈看起来像top-&gt; {10,10,4,5,1,2,7,5,10,8}&lt; -bottom。

现在从堆栈顶部开始弹出,保持计数器= 1,每次弹出计数器增加1,当你到达第n个元素时(在你的例子中第7个元素)停止弹出。

注意:这将以相反的顺序打印或检索数据/节点

答案 25 :(得分:0)

递归解决方案:

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;
    }
}

答案 26 :(得分:0)

职业杯书中给出的问题略有不同。它说找到单个链表的最后一个元素的第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;
        }
    }

答案 27 :(得分:0)

您还可以使用哈希表解决上述问题。哈希表的条目是节点的位置和节点的地址。因此,如果我们想从末尾找到第n个节点(这意味着m-n + 1来自第一个节点,其中m是节点数)。现在,当我们输入散列表条目时,我们得到节点的数量。步骤是: -

1.遍历每个节点并在哈希表中进行相应的输入。

2.查看哈希表中的m-n + 1节点,我们得到地址。

时间复杂度为O(n)。

答案 28 :(得分:0)

我认为问题代码中存在一个缺陷,我想知道它是否已经从一本书中获取,这是怎么可能的......它可能正确执行但代码在逻辑上有些不正确。在for循环内...应该根据p2->next ! = NULL

检查if条件
 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到达终点..如果你发现我的答案不正确,可以自由判断