
Nth-to-Last Element in a Singly Linked List - dogma
http://www.mytechinterviews.com/nth-to-last-element-in-a-singly-linked-list
======
cperciva
The author points out that a two-stage process consisting of 1. counting the
number of elements in the list, and then 2. finding the right element, is
quite inefficient -- but he then presents a different approach which uses two
pointers instead of one, yet is equally inefficient -- almost every pointer is
dereferenced twice.

Challenge: Show how, using _three_ pointers, you can find the _k_ th last
element from a linked list of unknown length _N_ with no more than _N + O(k)_
pointer dereferences.

~~~
basman
That's a neat one. My solution: pointers A, B, and C. A proceeds through the
list. B is always at the largest multiple of k that is at or before A. C is at
B-k. Every k steps, set C=B and B=A. When A reaches the end, step C forward
B-A times.

~~~
extension
Neato.

If reference locality is a big concern and k is small, the following might
perform better: allocate a ring buffer of size k+1 (ideally on the stack),
enqueue the pointers as you go, when you hit the end, return the tail of the
buffer.

Among other things, this problem well illustrates how the rules of
optimization can vary wildly depending on low level architecture.

~~~
cperciva
That doesn't help significantly, since you only save the final _O(k)_
dereferences. The situation (small _k_ ) where it's practical to allocate a
ring buffer like this is exactly the situation where it isn't useful.

~~~
extension
Actually, you're right, it's the larger values of k where this might make a
difference. If the list items are scattered in memory, the cost of the buffer
increases very slowly with k compared to the cost of hitting those extra k/2
items.

------
raganwald
The existence of posts like this really highlights the uselessness of "Guess
the answer I'm looking for" interview questions. It turns them into "Did you
take the trouble to scour the Internet for tech interview questions?"

That being said, if I ever have to interview another programmer who reads zero
programming blogs or web sites... I am going to end it all and become a bike
messenger.

~~~
boredguy8
I just had that interview. I called it "guess the word" because it was a
vocabulary nightmare. "What makes a good program?" "Not crashing is a good
start!" They were not impressed.

------
fexl
Naturally my first inclination is to write the thing in a completely self
contained purely functional form, which I do below. I think this will be
fairly efficient under lazy evaluation, since the bottom line is to count off
the given number of positions from the front of the list, and then use the
tail of _that_ list to step through the original list.

Here's the whole thing starting from scratch:

    
    
      # Optional values (also known as the "Maybe" type) 
      #
      # The (absent) function represents a value that is absent.
      # The (present value) function represents a value that is present.
    
      \absent = (\absent\present absent)
      \present = (\value \absent\present present value)
    
      # Natural numbers. 
      #
      # The (zero) function represents the number 0.
      # The (succ n) function represents the number 1+n (the successor of n).
      #
      # A natural number is really just an optional predecessor,
      # so the constructors are just synonyms for the Maybe type.
    
      \zero=absent
      \succ=present
    
      # Lists.
    
      \null = (\null\cons null)
      \cons = (\head\tail \null\cons cons head tail)
    
      # Subtraction of natural numbers.  Computes z = x - y.
      #
      # Returns (present z) if x >= y.
      # Returns absent if x < y.
    
      \sub = (\x\y
        y
          (present x)       # y = zero
        \yp
          x
            absent          # y = (succ yp) and x = zero
            \xp sub xp yp   # y = (succ yp) and x = (succ xp)
        )
    
      # Compute the length of a list.
    
      \len = (\list list zero \head\tail succ (len tail))
    
      # Return the item at the position in the list, starting with 0.
    
      \item = (\list\pos
        list
        absent       # list = null
        \head\tail   # list = (cons head tail)
          pos
            (present head)     # pos = zero, item found
            \np item tail np   # pos = (succ np), keep looking
        )
    
      # Return the item at the position from the end of the list, starting with 0.
    
      \item_end = (\list\pos
        sub (len list) (succ pos)  # Subtract 1+pos from length of list
        absent    # that's out of bounds, no item found
        \new_pos item list new_pos   # look for item normally at new pos
        )

------
bmalicoat
As someone currently doing the technical interview tour, I really appreciate
this link. Particularly that the author follows the general thought pattern of
thinking of an inefficient way and then searching for better ways. Does anyone
have any other favorite sites that have these types of questions with answers?

~~~
goodgoblin
One I got recently - and blanked on - write a function to generate all the
permutations of a string. Recursion preferred.

~~~
bshep
Quick and Dirty(probably better ways to do it):

    
    
      def permute(string, prefix = ''):
            if len(string) == 1:
      		print prefix + string
      		return
      
            for x in range(0, len(string)):
      		new_prefix = prefix + string[x]
      		unused_chars = string[0:x] + string[x+1:]
      		permute(unused_chars, new_prefix)

------
machrider
Alternatively, you could recognize that your data structure doesn't suit your
needs, and use a doubly-linked list.

~~~
cperciva
There are many situations where the cost of using a more sophisticated data
structure outweighs the advantages of avoiding an occasional excessively slow
operation.

~~~
machrider
We don't have enough information to know which situation this is. Hence, it's
an "alternative" to consider.

~~~
roundsquare
You're missing the point. With the question setup, you are supposed to assume
that a doubly linked list is not possible, for whatever reason. Its meant to
be an algorithm question, not a data structures question.

