我目前正在阅读“Crack the Coding Interview”一书(第5版)。其中存在一个特殊的问题,即实现一种算法来查找单个链表的最后一个元素的kth。这个问题讨论了各种方法。我对递归解决方案感到特别困惑。它如下(无耻地从书中复制):
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;
}
我的问题是:当我们找到特定节点时,我们将返回LinkedListNode
,即使我们不找到该特定节点,我们也会返回LinkedListNode
。所以没有办法告诉任何其他节点的第k个节点。任何人都可以告诉我,如果我的观察是否正确?如果是,我们如何才能使其正确(当然,除了在System.out.println
之前执行return head
因为那只是打印节点而不是返回它)?
答案 0 :(得分:2)
这个递归函数的技巧是递归调用首先发生 - 这意味着当调用执行后的任何代码时,我们已经在最后一个节点。一旦我们结束,我们的函数将返回null
,因为没有任何东西(头部现在是null
)。我们的callstack的最终函数返回null
,这意味着node = null
。现在函数检查当前节点是否是第k个节点(使用累加器i
)。
有两种可能的情况:
我们不在正确的节点:我们的函数只返回node
,我们记得null
。因此,对于callstack中的下一个更高的函数,除了i
更大之外,事情看起来就好像它已经结束了。
我们位于正确的节点:我们返回head
,而不是node
(请记住node = null
)。在这种情况下,我们会再次传播回调用堆栈,但这一次而不是node
在每一层都是null
,它是head
,这是我们想要的节点。最终我们回到顶部,并返回正确的元素。
所以,简单地说,函数一直返回null
直到找到元素,然后它继续返回元素。
糟糕的mspaint图片:
答案 1 :(得分:2)
简短回答:您的观察结果不正确,示例按原样运行。对于扩展的第一个简单的回忆例子:
因子:n! = n*(n-1)*(n-2)...*1
递归函数:
int fact(int n) {
if (n==1) {
return 1;
} else {
return n*fact(n-1);
}
}
所以基本上这个函数调用自己直到它到达n==1
的点,然后在结果到达你调用的那个之后得到一个结果。所以基本上
fact(3) = 3*fact(2) = 3*2*fact(1) = 3*2*1
在您的示例中也是如此:
首先通过列表中的下一个元素
调用自己来运行列表直到结束LinkedListNode node = nthToLastR2(head.next, k, i);
当它到达最后一个节点时,它开始返回东西
if(head == null){
return null;
}
只有这样,在该呼叫之前的最后一个才能进入
行i.value = i.value + 1;
并从最后一个节点开始计数。
然后通过检查
检查它是否具有最后一个kth节点if(i.value == k) {
如果它有它返回头部。
如果它还没有返回从之前返回的那个。例如k=4
和七个节点
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
每个步骤都是对同一函数的独立评估,不知道是谁调用它并等待它调用的函数返回的东西。
答案 2 :(得分:0)
我不确定你使用的书是那么好。他们在没有getter / setter的情况下访问成员变量。你会在15年前看到Java,但现在是一个很大的禁忌。他们正处于第5版(2011年),但似乎他们懒得没有更新他们的例子,因为Java惯例正在发展。
他们的递归解决方案非常复杂。我在下面写了一个替代的递归解决方案。它保留最新访问节点的队列。该队列保持尽可能小。我正在使用Java 8的Optional,其中返回的空值表示列表太短。
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);
}
}
最初使用空队列调用它:
recur(list, k, new ArrayDeque())
[警告:此段落处于高级水平。]
似乎我的解决方案通过保留队列来浪费内存,但是他们的解决方案将项目保留在堆栈中的内存中。我的解决方案是尾递归,而他们不是。不幸的是,由于JVM没有针对尾递归进行优化,因此暂时不是优势。 JVM还为我的方法保留堆栈的所有内容(包括队列的所有副本)。至少我认为我的解决方案更简单。