我正在用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
}
答案 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;
}
请注意此处的改进:
.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;
}