【问题标题】:Reversing first K nodes of Linked List,Why recursion executing twice for last iteration反转链表的前K个节点,为什么最后一次迭代执行两次递归
【发布时间】:2016-08-08 16:16:23
【问题描述】:

在解决反转链表的前 K 个元素的问题时,我编写了以下递归代码,但最后一次迭代执行了两次,即对于 k=1,函数调用 reverseNode() 发生了两次。任何机构都可以为什么会这样。如果我做错了什么,请纠正我。

Example : 

    If input is 

    1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 

    and k = 4 then output is

    4 -> 3 -> 2 -> 1 -> 5 -> 6 -> 7 -> 8 

public void reverseListRecursion(int k) {
    this.reverseNode(null, headNode,k);
}

public void reverseNode(Node node, Node nextNode,int k) {
    while (k > 0) {
        k = k-1;
        this.reverseNode(node, nextNode.next,k);
    }

    if (k == 0) {
        this.kNode = nextNode;
        this.kNode.next = null;
    }

    if (node == null) {
        nextNode.next = this.kNode;
    } else {
        nextNode.next = node;
    }
}

我的逻辑的工作代码按预期工作。但是当我尝试在“if”条件下使用变量“k”而不是“presentCounter”时,它就会出错。任何人都可以告诉我原因。

public void reverseListRecursion(int count) {
    this.reverseNode(null, headNode, count);
    System.out.println("\n");
    this.display(this.headNode);
}

/*
 * Condition K <= Length of linked list.
 */
public void reverseNode(Node node, Node nextNode, int k) {
    int presentCounter = k;
    if (k > 1) {
        k = k - 1;
        this.reverseNode(nextNode, nextNode.next, k);
    }
    if (presentCounter == 1) {
        this.kNode = nextNode.next; // Saving K's Next Node
        this.headNode = nextNode; // Setting K node as head node
    }
    if (node == null) {
        nextNode.next = this.kNode;
    } else
        nextNode.next = node;
}

【问题讨论】:

  • (请提供注释代码,说明您不满意的地方,并尝试将示例与代码分开。)
  • 我不确定我是否理解了这个问题;实现的输出是否符合预期?好像是这样。
  • 只需在调试器中单步执行即可查看发生了什么。
  • 你最好使用递归或循环,但不能同时使用。尝试将while 替换为if

标签: java algorithm recursion data-structures linked-list


【解决方案1】:

您在问题中提供的此代码存在几个问题:

public void reverseListRecursion(int k) {
    this.reverseNode(null, headNode,k);
}

public void reverseNode(Node node, Node nextNode,int k) {
    while (k > 0) {
        k = k-1;
        this.reverseNode(node, nextNode.next,k);
    }
    if (k == 0) {
        this.kNode = nextNode;
        this.kNode.next = null;
    }
    if (node == null) {
        nextNode.next = this.kNode;
    } else {
        nextNode.next = node;
    }
}
  1. wile 循环将使递归调用的次数增加到 k!。也许这是你的意图,但它肯定不会成为一个有效的算法。这项工作可以通过移动 k-1 个节点来完成,因此进行 k! 次调用并不是那么高效。

  2. 第一个参数 (node) 永远不会改变:递归调用只是传递相同的参数,因此您在初始调用 (null) 中传递的值就是这个参数每次通话都会出现。因此,最后一个if 条件的结果将始终相同。这看起来不对。

  3. 第一个 if 条件将始终为真,因为它与前面的 while 条件相反。所以应该没有必要对k == 0进行测试。

  4. 第一个if 块中的代码使this.kNode 成为nextNode 的同义词,然后将其next 属性设置为null。根本没有理由将任何next 属性设置为null。如果有的话,它会破坏链表。

  5. 在第二个 if 块中,nextNodenext 属性设置为 ... nextNode(参见上一点,这表明 this.kNodenextNode 同义) .所以现在你有了一个自引用节点,这确实是你永远不想拥有的东西。此代码使前 k+1 个节点自引用,从而有效地将它们从原始链表中分离出来。

  6. 初始调用使用 headNode 作为第二个参数。这个变量显然是你所在类的私有成员。但是,在执行反转之后,headNode 仍将引用它在调用之前引用的节点。顾名思义,它应该指向列表中的第一个节点,但是由于反转会将另一个节点移到最前面,所以 headNode 完成后会指向错误的节点。在反转之后,您没有其他变量或属性将指向列表中的第一个节点。 this.kNode 可能是它,但语句 this.kNode.next = nullnextNode.next = this.kNode 不是你会对列表的第一个节点执行的操作。

此代码存在太多问题,无法清楚地了解您实际尝试执行的操作。

我建议使用这个算法,通过例子来解释:

list = 1 2 3 4 5 6 7 8
k = 4

将原始第一个节点之后的节点移动到列表的头部

list = 2 1 3 4 5 6 7 8 
k = 3

将原始第一个节点之后的节点移动到列表的头部

list = 3 2 1 4 5 6 7 8 
k = 2

将原始第一个节点之后的节点移动到列表的头部

list = 4 3 2 1 5 6 7 8 
k = 1

由于 k = 1,因此不必再进行移动。

这就是您的代码的外观:

public void reverseListRecursion(int k) {
    // The reversal returns the node that took the first position
    this.headNode = this.reverseNode(this.headNode, k, this.headNode);
};

public Node reverseNode(Node origFirstNode, int k, Node currFirstNode) {
    // If 1 element needs to be reversed, there is nothing to do:
    if (k <= 1) return currFirstNode;

    // Move the node after the original first node before the current first node
    Node movingNode = origFirstNode.next;
    origFirstNode.next = movingNode.next;
    movingNode.next = currFirstNode;

    // The moved node is now the current first node. Repeat:
    return this.reverseNode(origFirstNode, k-1, movingNode);
};

这个解决方案的好处是reverseNode 方法不需要引用this.headNode,因此它也可以用于反转列表中间的元素。您可以添加此方法,该方法将节点作为第二个参数:

public void reverseListRecursionAfter(int k, Node afterNode) {
    afterNode.next = this.reverseNode(afterNode.next, k, afterNode.next);
};

这将反转给定节点之后的节点。

这是一个实时的 sn-p,将相同的代码翻译成 JavaScript(仅用于演示):

// Node class
function Node(val, next) {
    this.val = val;
    this.next = next;
    this.toString = function (cascade) {
        if (!cascade || this.next === null) return '(' + this.val + ')';
        if (this.next === this) return '(' + this.val + ')-loop';
        return '(' + this.val + ')->' + this.next.toString(true); 
    }
}

// List class
function List() {
    this.headNode = null;
    
    this.reverseListRecursion = function(k) {
        // The reversal returns the node that took the first position
        this.headNode = this.reverseNode(this.headNode, k, this.headNode);
    };

    this.reverseNode = function(origFirstNode, k, currFirstNode) {
        // If 1 element needs to be reversed, there is nothing to do:
        if (k <= 1) return currFirstNode;

        // Move the node after the original first node before the current first node
        var movingNode = origFirstNode.next;
        origFirstNode.next = movingNode.next;
        movingNode.next = currFirstNode;
        // The moved node is now the current first node. Repeat:
        return this.reverseNode(origFirstNode, k-1, movingNode);
    };

    this.insert = function (arr) {
        for (var i = arr.length - 1; i >= 0; i--) {
            this.headNode = new Node(arr[i], this.headNode);
        }
    }
        
    this.toString = function () {
        return '{' + this.headNode.toString(true) + '}';
    }
}

var output = [];

// Sample data
var list = new List();
list.insert([1, 2, 3, 4, 5, 6, 7, 8]);
output.push('before: ' + list);

// Make the reversal call
list.reverseListRecursion(4);
output.push('after: ' + list);

// Show result in snippet
document.write(output.join('<br>'));

【讨论】:

    【解决方案2】:

    你的递归应该是

    if (k > 0) {  // and not while 
        k = k-1;
        this.reverseNode(node, nextNode.next,k);
    }
    ...
    

    【讨论】:

      猜你喜欢
      • 2015-03-05
      • 2018-07-28
      • 2021-08-02
      • 2020-12-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-31
      • 1970-01-01
      相关资源
      最近更新 更多