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 kth last element from a linked list of unknown length N with no more than N + O(k) pointer dereferences.
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.
Yep, that's it. I like this puzzle because while there are lots of examples where having 2 pointers is better than 1, it's the only (non-contrived) example I can think of where 1 point is just as good as 2, but 3 pointers is much better.
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.
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.
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.
True. But the author hadn't gotten to the point of counting operations yet, so I don't think he was trying to make a subtle point about memory hierarchies and non-constant access times. :-)
What about going through the list once, put each node onto a stack, then pop n - 1 elements off the stack, and the nth last element will be on the top of the stack.
Edit: uses too many pointers as cperciva points out below.
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.
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.
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
)
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?
It's not a website, but _Programming Pearls_ by Jon Bentley is still an excellent way of getting into an algorithms frame of mind. Don't be dissuaded just because it's a few years old. I like the way it presents the thought process of solving the given problems.
There are many situations where the cost of using a more sophisticated data structure outweighs the advantages of avoiding an occasional excessively slow operation.
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.
Challenge: Show how, using three pointers, you can find the kth last element from a linked list of unknown length N with no more than N + O(k) pointer dereferences.