简体   繁体   中英

find nth element from the last in a list using recursion

we can solve this problem in, say C, using a static variable, like in the snippet below ( like the function found in this page ).

static int i = 0;
if(head == NULL)
   return;
getNthFromLast(head->next, n);
if(++i == n)
  {THIS IS THE NTH ELEM FROM LAST
   DO STUFF WITH IT.
  }
  1. I'm trying to see if I can solve this using a tail call recursion, and NO static variables/global variables.

  2. I'm trying to learn Haskell was wondering how to implement this in a purely functional way, with out using Haskell's length and !! something like x !! ((length x) - K) x !! ((length x) - K) .

So started by asking, how we can do it in C, with recursion and NO static/global variables, just to get some idea.

Any suggestions/pointers would be appreciated.

Thanks.

The linked page explains how to solve the problem with a two-finger solution; it's a bit surprising that they don't simply write the recursive version, which would be simple and clear. (In my experience, you don't succeed at interviews by providing tricky and obscure code when there is a simple and clear version which is equally efficient. But I suppose there are interviewers who value obscure code.)

So, the two-finger solution is based on the observation that if we have two pointers into the list (the two fingers), and they are always n elements apart because we always advance them in tandem, then when the leading finger reaches the end of the list, the trailing finger will point at the element we want. That's an easy tail recursion:

Node* tandem_advance(Node* leading, Node* trailing) {
  return leading ? tandem_advance(leading->next, trailing->next)
                 : trailing;
}

For the initial case, we need the leading finger to be N elements from the beginning of the list. Another simple tail recursion:

Node* advance_n(Node* head, int n) {
  return n ? advance_n(head->next, n - 1)
           : head;
}

Then we just need to put the two together:

Node* nth_from_end(Node* head, int n) {
  return tandem_advance(advance_n(head, n + 1), head);
}

(We initially advance by n+1 so that the 0th node from the end will be the last node. I didn't check the desired semantics; it might be that n would be correct instead.)

In Haskell, the two-finger solution seems to be the obvious way. This version will go wrong in various ways if the requested element isn't present. I'll leave it as an exercise for you to fix that (hint: write versions of drop and last that return Maybe values, and then string the computations together with >>= ). Note that this takes the last element of the list to be 0th from the end.

nthFromLast :: Int -> [a] -> a
nthFromLast n xs = last $ zipWith const xs (drop n xs)

If you want to do some of the recursion by hand, which No_signal indicates gives better performance,

-- The a and b types are different to make it clear
-- which list we get the value from.
lzc :: [a] -> [b] -> a
lzc [] _ = error "Empty list"
lzc (x : _xs) [] = x
lzc (_x : xs) (_y : ys) = lzc xs ys

nthFromLast n xs = lzc xs (drop n xs)

We don't bother writing out drop by hand because the rather simple-minded version in the library is about the best possible. Unlike either the first solution in this answer or the "reverse, then index" approach, the implementation using lzc only needs to allocate a constant amount of memory.

I assume your code is missing

getNthFromLast(list *node_ptr, int n) {

right at the top. (!!)

Your recursive version keeps track of node_ptr s in its call stack frames, so it is essentially non tail-recursive. Moreover, it continues to unwind the stack (go back up the stack of call frames), while incrementing the i and still checking its equality to n , even after it had found its goal n th node from the last; so it is not efficient.

That would be an iterative version, indeed encodable as tail-recursive, that does things on the way forward, and so can stop immediately after reaching its target. For that, we open up the n -length gap from the start, not after the end is reached. Instead of counting backwards as your recursive version does, we count forward. This is the same two-pointers approach already mentioned here.

In pseudocode,

end = head; 
repeat n: (end = end->next); 
return tailrec(head,end)->payload;

where

tailrec(p,q) { 
    if(NULL==q): return p; 
    else: tailrec(p->next, q->next);
}

This is 1-based, assuming n <= length(head) . Haskell code is already given in another answer.

This technique is known as tail recursion modulo cons , or here, modulo payload -access.

nthFromLast lst n = reverse lst !! n

Because Haskell is lazy, this should be sufficiently efficient

If you don't want to use !! , you'll have to redefine it yourself, but this is silly.

The typical iterative strategy uses two pointers and runs one to n - 1 before starting to move the other. With recursion, we can instead use the stack to count back up from the end of the list by adding a third argument. And to keep the usage clean, we can make a static helper function (in this sense it means only visible within compilation unit, a totally different concept to a static variable with function scope ).

static node *nth_last_helper(node* curr, unsigned int n, unsigned int *f) {
  node *t;
  if (!curr) {
    *f = 1;
    return NULL;
  }
  t = nth_last_helper(curr->next, n, f);
  if (n == (*f)++) return curr;
  return t;
}

node* nth_last(node* curr, unsigned int n) {
  unsigned int finder = 0;
  return nth_last_helper(curr, n, &finder);
}

Alternatively we could actually do the counting without the extra parameter, but I think this is less clear.

static node *nth_last_helper(node* curr, unsigned int *n) {
  node *t;
  if (!curr) return NULL;
  t = nth_last_helper(curr->next, n);
  if (t) return t;
  if (1 == (*n)--) return curr;
  return NULL;
}

node* nth_last(node* curr, unsigned int n) {
  return nth_last_helper(curr, &n);
}

Note that I have used unsigned integers to avoid choosing semantics for the "negative nth last" value in a list.

However, neither of these are tail recursive. To achieve that, you can more directly convert the iterative solution into a recursive one, as in rici's answer .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM