Nth-to-Last Element in a Singly Linked List

Question: Devise an algorithm to determine the Nth-to-Last element in a singly linked list of unknown length. If N = 0, then your algorithm must return the last element.

Answer: Given that the only way to traverse a singly linked list is forwards from the head, it is not possible to just count N elements from the end of the linked list. Furthermore, the length of the linked list is unknown. We could traverse the list once to determine the number of elements and then traverse the list again to stop at the Nth element from the last element. But this seems a little inefficient. What if we could use 2 pointers?

So what if we use a current pointer and another pointer that is N elements behind the current pointer. When the current pointer reaches the end of the list, the second pointer will be pointing to the element N elements from the end of the list.

Here’s some code to illustrate this logic.

Node * findNToLastNode( Node *head, int N )
{
    int i = 0;
    Node *current, *behind;
    current = head;
    for( i = 0; i < N; i++ ) {
        if( current->next ) {
            current = current->next;
        } else {
            return NULL;                  // Length of the list is less than N
        }
    }

    behind = head;
    while( current->next ) {
        current = current->next;
        behind = behind->next;
    }

    return behind;
}

If you're looking for some serious preparation for your interviews, I'd recommend this book written by a lead Google interviewer. It has 189 programming questions and solutions:

Book Cover
Button

17 Responses

  1. Skipy says:

    should it be i <= N ?

    public Node tail(int N) {
    Node explorer = head;
    Node cursor = head;

    for (int i = 0; i <= N; i++) {
    explorer = explorer.next;
    if (explorer.next == null) {
    return null;
    }
    }
    while (explorer!=null) {
    cursor = cursor.next;
    explorer = explorer.next;
    }
    return cursor;
    }

  2. Recurse says:

    Two things:
    First, your if statement should be

    if(current)

    Also, your while loop condition should be

    while(current)

    Let’s say you have 3 elements and you want the 3rd to last element(which is the 1st element). Your code return null instead of returning the 1st element.

  3. elf1435 says:

    recursive solution with one function :

    Node* findNToLastNode ( Node* head, int n )
    {
    static int _count ;
    static Node* _pNode = NULL ;

    if ( !head )
    {
    _count = 0 ;
    return head ;
    }
    findNToLastNode(head->next, n) ;
    if ( _count++ <= n) _pNode = head ;
    return _pNode ;
    }

  4. Doggie says:

    Matthew, how would you do N-th to last… not necessarily second to last.

  5. Matthew says:

    Oops, I made a mistake:

    while( current->next )
    {
    if (current->next->next = NULL)
    {
    second_to_last = current
    }

    current = current->next;
    }

  6. Matthew says:

    Do it in one loop!


    while( current->next )
    {
    current = current->next;

    if (current->next->next = NULL)
    {
    second_to_last = current;
    }
    }

  7. Srikanth says:

    Have two pointers displacer, identifier

    move the displacer initially to N positions

    then move the identifier from start to the end and at each step moving the displacer too.

    When the displacer reaches the end of the list the identifier will be at the N position from the last…

  8. vitaly says:

    O(n):
    Node * findR(Node *head, int N, int cur, int *n)
    {
    Node * res;
    if(Nnext)
    res = findR(head->next, N, cur+1, n);
    else if(N>cur)
    return NULL;
    if(N<=cur && cur==*n)
    res = head;
    return res;
    }

    Node * findNToLastNode( Node *head, int N )
    {
    int n=0, cur=0;
    return findR(head, N, cur, &n);
    }

  9. Daniel says:

    I think I’ve got a nice, short solution:

    int nthtolast(list_t* list, int n) {
    int len=0;
    list_t* i=list;

    while(list) {
    if(len>n) i=i->next;
    len++;
    list=list->next;
    }
    return i->key;
    }

    Thoughts?

  10. Jouni Osmala says:

    Firstly its not only cache behaviour thats this advantage, in OoO machine (every intel machine after pentium MMX with exception of atom) there is no dependence between the two traversals, so they can happen parallel in hardware, which should be by far closer to a speed of going it once than going it twice sequentially.

    There is a way of doing it without traversing it twice, just write the addresses in array of size [n] and do modulo operation on counter to get index in the temp array, and if next is not null write the address to array else read the address from array and return it.

    Anyway lists are evil. The pointers take lots of space in large addresspaces, and its traversal has data dependency that eliminates lots of automatic hardware parallerism at ipc level.

  11. Michael says:

    @Denis Molony: It is a single traversal. O(2*N) (2 to account for two pointers iterating through the list), which is in turn O(N).

    @OP: Nice post.

  12. Coderoom says:

    Or you could reverse the initial list on the first traversal by popping off the elements as you read them and pushing them onto a new singly-linked list. Then read N items from the start of that list => L+N traversals.

    Only useful if you don’t mind your singly-linked list ending up backwards, though 😉 I like your method better.

  13. To all those people saying “you’re traversing it twice”: yes, but note that the solution presented above is much nicer on the cache, for reasonable n, than traversing the whole list twice.

  14. Anonymous says:

    Use 3 pointers: current, lastMark, secondLastMark.

    Iterate through the list with current. Treat every (n+1)th element as a mark and note the last mark and second-to-last mark whenever encountering one. When you reach the end, figure out how far you have to step from secondLastMark to your answer, and do that.

  15. Phu Nguyen says:

    You can do improve on this by leapfrogging. Have behind stay behind by at most N, and have behind2N stay behind at most 2N. Move behind and behind2N at N boundaries.

  16. Denis Molony says:

    But you *are* traversing it twice.

    current = current->next; // first traversal
    behind = behind->next // second traversal

    You could prove it by doing some timings.

    If this was *really* a requirement, it would already be a doubly linked list.

  17. Paul McJones says:

    So you still have to perform 2L-N link traversals, where L is the length of the list. For long lists, your version might have better cache behavior than first computing L and then advancing (L-N) steps.

Leave a Reply

Using Gravatars in the comments - get your own and be recognized!

XHTML: These are some of the tags you can use: <a href=""> <b> <blockquote> <code> <em> <i> <strike> <strong>