Reputation: 871
I am currently reading the book "Crack the Coding Interview" (5th edition). There is a particular problem in it saying Implement an algorithm to find kth to last element of a singly linked list. There are various approaches discussed to the problem. I am particularly confused by the recursive solution. It goes as follows (shamelessly copying it from the book):
public class IntWrapper {
public int value = 0;
}
LinkedListNode nthToLastR2(LinkedListNode head, int k, IntWrapper i){
if(head == null){
return null;
}
LinkedListNode node = nthToLastR2(head.next, k, i);
i.value = i.value + 1;
if(i.value == k) { // We've found the kth element
return head;
}
return node;
}
My question is: We are returning a LinkedListNode
when we find that particular node and we are returning a LinkedListNode
even when we don't find that particular node. So there is no means to tell kth node from any other node. Can anyone please tell me if my observation is correct or not? If yes, how can we make it correct (other than, of course doing a System.out.println
before the return head
because that would be just printing the node and not returning it) ?
Upvotes: 3
Views: 1528
Reputation: 5326
I'm not sure that the book you are using is that good. They access member variables without getters/setters. You would see that 15 years ago in Java, but it's a big no-no now. They are in their 5th edition (2011), but it seems they were lazy in did not update their examples as the Java conventions were evolving.
Their recursive solution is quite complicated. I wrote a alternate recursive solution below. It keeps a queue of the latest visited nodes. That queue is kept as small as possible. I am using Java 8's Optional where a returned empty value signifies that the list is too short.
Optional<LinkedListNode> recur(
LinkedListNode currentNode,
int nFromLast,
Queue<LinkedListNode> latestNodes) {
if (currentNode == null) { // went past the last node
if (latestNodes.size() > nFromLast)
return Optional.of(latestNodes.remove());
else // the queue is too short
return Optional.empty();
} else {
if (latestNodes.size() == (nFromLast + 1))
latestNodes.remove();
latestNodes.add(currentNode);
return recur(currentNode.getNext(), nFromLast, latestNodes);
}
}
It is called initially with an empty queue:
recur(list, k, new ArrayDeque())
[Warning: this paragraph below is at an advanced level.]
It might seem that my solution wastes memory by keeping the queue, but their solution keeps items in memory on the stack. My solution is tail recursive and theirs is not. Unfortunately, it is not an advantage for the time being since the JVM does not optimize for tail recursion. The JVM also keeps everything (including all copies of the queue) on the stack for my method. At least I think my solution is simpler.
Upvotes: 0
Reputation: 3198
Short answer: Your observation is incorrect, the example works as is. To the expanation first simple recusion example:
Factorials: n! = n*(n-1)*(n-2)...*1
Recursive Function:
int fact(int n) {
if (n==1) {
return 1;
} else {
return n*fact(n-1);
}
}
So basically the function calls itself until it comes to the point where n==1
and then you get a result after the result has come through to the one you called. So basically
fact(3) = 3*fact(2) = 3*2*fact(1) = 3*2*1
The same happens in your example:
It starts by running through the list till the end by calling itself with the next element of the list
LinkedListNode node = nthToLastR2(head.next, k, i);
when it reaches the last node it starts returning stuff
if(head == null){
return null;
}
only then does the last one before that call go to the line
i.value = i.value + 1;
and starts counting from the last node.
Then checks if it has the kth node from the last by checking
if(i.value == k) {
and if it has it return the head it got.
If it hasn't it return the one it got returned from the one before. For example k=4
and seven node
head: 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> null
| returning
i= 7 6 5 4 3 2 1 v
3 <- 3 <- 3 <- 3 <-.-null<- null <- null
Each step is an independent evaluation of the same function knowing not who called it and waiting for the one it called to return something.
Upvotes: 2
Reputation: 2492
The trick in this recursive function is that the recursive call happens first - this means that by the time any of the code after the call executes, we'll already be at the last node. Once we get to the end, our function will return null
because there is nothing left (head is now null
). The final function of our callstack returns null
, which means that node = null
. Now the function checks to see if the current node is the k-th one (using the accumulator i
).
There are two possible cases:
We aren't at the correct node: Our function simply returns node
, which as we recall is null
. Thus for the next higher function in the callstack, things look just like if it had hit the end, except that i
is larger.
We are at the correct node: We return head
, not node
(remember node = null
). In this case, we propagate back up the callstack again, but this time instead of node
being null
at each layer, it's head
, which is the node we want. Eventually we get back to the top, and return the correct element.
So, simply put, the function keeps returning null
until we find the element, then it keeps returning the element.
Poorly drawn mspaint picture:
Upvotes: 3