交换链表算法的相邻元素

时间:2018-10-21 19:35:30

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

我正在用Java练习链表编程问题,我对以下问题有一个有效的解决方案,但无法理解它的工作原理。

我已经在每一行旁评论了我认为应该发生的事情,但是显然我还没有掌握这些工作的原理,有人可以解释我的评论哪里有问题以及该解决方案是正确的。 h表示头部,s表示缓慢等)

给出一个链表,每隔两个相邻节点交换一次并返回其头部。 例: 给定1-> 2-> 3-> 4,您应该将列表返回为2-> 1-> 4-> 3。

public Node s(Node head) {
    // 1(h)-2-3-4 passed in
    // if only 1 node or null node return it
    if (head == null || head.next == null) {
        return head;
    }

    Node slow = head.next;   // 1h-2s-3-4
    head.next = head.next.next; // 1h-3-4
    slow.next = head; // 2s-1h-3-4
    head = slow; // 2s/h-1-3-4
    Node parent = slow.next; // 1p-3-4
    slow = slow.next.next; // 3s-4

    while (slow != null && slow.next != null) {
        Node temp = slow.next;  // 4t-null
        slow.next = slow.next.next; // 3s-null
        temp.next = slow;    // 4t-3s-null
        parent.next = temp; // 1p-4-3
        parent = parent.next.next; // 3p=null
        slow = slow.next; // 4-null, loop ends cause next to slow is null
    }
    return head; // ( head = slow from earlier) 4-null 
}

3 个答案:

答案 0 :(得分:1)

代替交换节点,我们只能交换数据,这将很容易并且将获得所需的输出。

 public Node s(Node head) { 
        if (head == null || head.next == null) {
            return head;
        }
        Node temp = head; 

        /* Traverse only till there are atleast 2 nodes left */
        while (temp != null && temp.next != null) { 

            /* Swap the data */
            int k = temp.data; 
            temp.data = temp.next.data; 
            temp.next.data = k; 
            temp = temp.next.next; 
        }
        return head;
    }

答案 1 :(得分:1)

让我们假设一个A -> B -> C -> D的链接列表。

为了方便讨论,我在代码中对行进行了编号。

 1 public Node s(Node head) {
 2     // if only 1 node or null node return it
 3     if (head == null || head.next == null) {
 4         return head;
 5     }
 6 
 7     Node slow = head.next;
 8     head.next = head.next.next;
 9     slow.next = head;
10     head = slow;
11     Node parent = slow.next;
12     slow = slow.next.next;
13
14     while (slow != null && slow.next != null) {
15         Node temp = slow.next;
16         slow.next = slow.next.next;
17         temp.next = slow;
18         parent.next = temp;
19         parent = parent.next.next;
20         slow = slow.next;
21     }
22     return head;
23 }

在第7行,slow指向节点B。在第8行,head.next设置为B的后继者C,在第9行,B指向A,在第10行, head指向B。我的评论显示了发生的情况。

 7     Node slow = head.next;      // slow = B
 8     head.next = head.next.next; // head.next = C
 9     slow.next = head;           // B.next = A (because head points to A)
10     head = slow;                // head = B

该代码交换了前两个节点。您的列表现在看起来像这样:

B -> A -> C -> D

现在,由于命名不正确,代码变得有些混乱。 slow当前指向B。

11     Node parent = slow.next;  // parent = A
12     slow = slow.next.next;    // slow = C

请记住,slow现在指向C。这是接下来发生的事情:

14     while (slow != null && slow.next != null) {
15         Node temp = slow.next;      // temp = D
16         slow.next = slow.next.next; // C.next = D.next (which is null)
17         temp.next = slow;           // D.next = C
18         parent.next = temp;         // A.next = D

这时,节点C和D已被交换,而A根据需要指向D。现在,列表看起来像B -> A -> D -> C

循环中的最后两行只是为下一次设置了内容。请记住,parent指向A。

19         parent = parent.next.next;  // parent = C
20         slow = slow.next;           // slow = null

循环回到顶部,我们看到slow == null,因此循环退出。

尽管您发布的代码行之有效,但这却不必要地造成混乱。进入循环之前,无需对前两个节点进行特殊交换,并且变量名可能更具描述性。

要交换两个节点,必须将第二个指向第一个,并将第一个指向第二个的后继。为此,您必须在覆盖第二个后继者之前保存它。例如,如果您有A -> B -> C并且想要B -> A -> C,则必须这样做,假设head指向A:

firstNode = head // firstNode points to A
secondNode = firstNode.next  // secondNode points to B
secondNodeSuccessor = secondNode.next // this points to C
secondNode.next = firstNode  // B now points to A
firstNode.next = secondNodeSuccessor  // A now points to C
head = secondNode  // and head points to B

此时,secondNodeSuccessor指向C,这是下一个firstNode

了解如何交换节点后,您可以简化代码:

public Node s(Node head) {
    // if fewer than 2 nodes, return.
    if (head == null || head == null) {
        return head;
    }

    // we know that the new head will be the second node.
    Node firstNode = head;
    Node parentNode = null;

    while (firstNode != null && firstNode.next != null) {
        Node secondNode = firstNode.next;
        Node secondNodeSuccessor = secondNode.next;

        // swap the nodes
        secondNode.next = firstNode;
        firstNode.next = secondNodeSuccessor;

        if (parentNode != null) {
            // This links the previous node (the one right before
            // the two that we just swapped) to the swapped nodes.
            parentNode.next = secondNode;
        }
        // the new parent node is the last swapped node.
        parentNode = firstNode;
        firstNode = firstNode.next; // set up for next pair
    }

    return head.next;
}

请注意此处的改进:

  1. 我取消了前两个节点的特殊情况交换,通过使每个交换相同来简化事情。
  2. 有意义的变量名使我要引用的节点清楚可见。
  3. 消除.next.next的构造可以使代码推理变得更容易,并且还可以更轻松地确定代码是否可能取消引用null。

您的调试器是一个非常有用的工具,可帮助您了解代码的工作方式。如果要在调试器中单步执行代码,则可以检查变量并查看每一行代码如何影响状态。如果您不知道如何使用调试器,则应该立即花时间学习。这将节省您小时的调试时间,也将大大增加您对代码工作原理的了解。

答案 2 :(得分:0)

另外两个解决方案要么不符合您的要求,要么为某些输入提供错误的结果。

我提出了一种替代方法,我已经在 LeetCode 上测试过(如果您想自己测试,请务必将 Node 类型重命名为 ListNode)。 我希望我添加到代码中的注释足够清楚。如有疑问,我建议尝试在交互式调试器中执行此过程。

public ListNode s(Node head) {
    // if the list is empty or it's a singleton, no node needs to be swapped
    if (head == null || head.next == null) {
        return head;
    }
  
    // first and second are the first and second node of the current pair to swap
    Node first = head;
    Node second = head.next;
    
    // parent is the node immediately before the current pair.
    // Initially, there is no such pair
    Node parent = null;
    
    // the updated list starts from the second node of the first pair
    head = second;
    
    // iterate until there is a valid pair to swap
    while (first != null && second != null) {
        // swap the two nodes of the current pair
        first.next = second.next;
        second.next = first;
        
        if (parent != null) {
            // attach the second element to the updated node of the previous pair
            parent.next = second;
        }
        
        // keep the invariant of parent valid: parent precedes the new pair to swap,
        // if such a pair exists
        parent = first;

        // advance the pointers of the first and second elements of the new pair to swap
        first = first.next;            
        second = (first == null) ? null : first.next;
    }
      
    return head;
}