给定一个单链表,将所有奇数节点组合在一起,然后是偶数节点
Posted
技术标签:
【中文标题】给定一个单链表,将所有奇数节点组合在一起,然后是偶数节点【英文标题】:Given a singly linked list, group all odd nodes together followed by the even nodes 【发布时间】:2016-03-12 19:05:24 【问题描述】:我遇到了这个问题:
。请注意,这里我们讨论的是节点编号,而不是节点中的值。
您应该尝试在适当的位置进行操作。程序应该以 O(1) 的空间复杂度和 O(nodes) 的时间复杂度运行。
例子:
Given 1->2->3->4->5->NULL, return 1->3->5->2->4->NULL.
我看到了解决方案但我不明白,我需要有人直观地解释问题的解决方案。
这是一个解决方案,但它是用 Java 编写的。我无法想象指针操作,有人可以帮我画出发生了什么吗?
public ListNode oddEvenList(ListNode head)
if(head == null || head.next == null)
return head;
ListNode odd = head;
ListNode even = head.next;
ListNode evenHead = even;
while(odd.next != null && even.next != null)
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
odd.next = evenHead;
return head;
【问题讨论】:
很难看出你对这个简单的编程作业有什么问题。更具体地说明您的困难是什么。 @ÖöTiib 我无法理解指针操作,我需要直观地了解它们是如何构造的。 那你需要先了解什么是指针以及它们是如何被操作的? 我知道什么是指针,但是解决方案的赋值让我很困惑,有很多指针赋值,但是我无法在纸上想象它们,它们是如何使用的 【参考方案1】:你已经得到了很好的详细答案,所以我觉得发布这个有点愚蠢,但我需要一些时间来制作图纸,所以无论如何都在这里。
我将解释您发布的算法,因为它可以直接用 C++ 翻译(我之前认为节点的“奇数”是由它的值而不是它的位置决定的,这需要修改带有指向指针的头部)。
我只是要直观地解释这个算法,我不会对这实际上只是一种“压缩”的方式来解释这个算法,它实际上是一种拥有两个链表(一个用于奇数元素和一个用于偶数元素)的“压缩”方式,以及如何处理指针。 如 cmets 所述,有专门的书籍。
public ListNode oddEvenList(ListNode head)
//Check that there are AT LEAST TWO ELEMENTS
if (head == null || head.next == null)
return head;
//Initialize pointers
ListNode lastOdd = head;
ListNode lastEven = head.next;
ListNode firstEven = lastEven;
//Continue as long as there is at least two more elements
while (lastOdd.next != null && lastEven.next != null)
//Connect the last odd element with the element next to the
//last even one (such element is odd)
lastOdd.next = lastEven.next;
//Advance lastOdd to such element
lastOdd = lastOdd.next;
//Do the same for the even list, only inverting the roles
lastEven.next = lastOdd.next;
lastEven = lastEven.next;
//Now connect the last odd element with the first even element
//This join the two list together
lastOdd.next = firstEven ;
return head;
视觉上
//Initialize pointers
ListNode lastOdd = head;
ListNode lastEven = head.next;
ListNode firstEven = lastEven;
循环的第一次迭代
lastOdd.next = lastEven.next;
lastOdd = lastOdd.next;
lastEven.next = lastOdd.next;
lastEven = lastEven.next;
所有其他迭代
正如您现在可以想象的那样,循环重复,而 lastEven
和 lastOdd
都有下一个元素,因为前者是后者之前的一个元素,这意味着有 列表中至少有两个元素未处理。
一旦循环结束,剩下的就是加入两个列表
【讨论】:
写得很好的答案。荣誉。【参考方案2】:假设您从一个单链表开始。它通常有一个头和尾指针(这里左右倾斜),每个链接都指向下一个。
在下图中,偶数节点为蓝色,奇数节点为灰色。
解决这个问题的一种方法是保持 6 个指针:
指向您当前在原始列表中处理的链接的指针,以及指向原始列表尾部的指针。 指向已处理的偶数节点的头部和尾部的指针。 指向已处理的奇数节点的头部和尾部的指针。(这仍然是 O(1)。)
在上图中,原始列表(左侧)在右侧。处理后的偶节点列表在左上角,处理后的奇节点列表在左下角。
现在,当原始列表中仍有未处理的节点时,根据需要将当前节点移动到偶数或奇数列表中。最后,只需将一个点的尾部链接到另一个点的头节点即可。将原始列表的头尾指针设置为这个结果列表,就完成了。
【讨论】:
如果我没有尾指针怎么办?解决方案不包含尾指针 你必须至少有一个指针才能知道列表从哪里开始(否则无法解决)。遍历它,直到下一个指针为空,并且您拥有另一个。这不会改变时间复杂度。 我知道链表是什么,但我无法解决问题,您推荐阅读哪些书籍或如何解决此类编程问题?我可以用你的Skype吗,我需要和你谈谈,我需要向你学习 要了解这些东西,我强烈推荐Introduction to Algorithms,这是一本很好读的书。如果您需要有关主题的进一步帮助,请在本网站上进一步询问(我也很乐意回答)。【参考方案3】:我不会写代码,而是给你一个非常简短的基本概念概要,希望它应该足够简单易懂。如果你开始画各种图表,我想你在这里很容易混淆。基本概念很简单的时候,这里就不用画复杂的图了
单链表中的尾指针
让我们定义什么是“尾指针”。尾指针是指向链表末尾的nullptr
的指针。这是一个双指针。
当单链表为空时:
node *head=0;
这就是问题中的nullptr
:
node **tailptr= &head;
tailptr
指向链表的末尾,也就是此时的头指针。
使用尾指针追加
拥有尾指针可以让您做什么?好吧,它允许您将一个新节点结束到列表的末尾,而不必从head
开始找到它。您已经知道如何将元素添加到链表的开头。现在,您可以在末尾添加一个,同样简单:
node *p=new node;
*tailptr=p;
p->next=0;
tailptr= &p->next;
如果您记住tailptr
指向列表末尾的nullptr
指针,那么这应该很有意义。您只需更改nullptr
以指向插入到列表末尾的新节点。而新节点的tailptr
,也就是nullptr
,因为它现在是新的最后一个节点,是新的尾指针。
结论
这就是完成这项家庭作业所需的全部知识。
您只需创建奇偶列表:
node *odd=0, *even=n0;
声明它们的尾指针:
node **tailptrs[2]= &odd, &even;
int next_one=0;
然后,遍历原始单链表,将每个条目附加到tailptrs[next_one]
,然后翻转它:next_one=1-next_one
。因此,当您遍历原始列表时,每个节点都会附加到奇数和偶数列表中,在两者之间交替。这比您展示的基于 Java 的复杂逻辑还要简单。
您需要正确处理的棘手部分是记住节点的原始next
仍然是原始链接列表的一部分。一个技巧是更新每个tailptr
不将当前节点的next
设置为nullptr
,但等到原始单链表完全迭代,然后简单地设置两者最后的尾指针明确指向nullptr
。
【讨论】:
【参考方案4】:想法是继续收集奇数节点(开始时),同时跟踪奇数和偶数指针。
void groupOddNode()
Node o,e,p,q;
o= head;
p= head;
e= head.next;
while(p.next!= null && p.next.next!= null)
p=p.next;
q=p.next;
p.next = q.next;
q.next = e;
o.next = q;
e=q.next;
o=o.next;
例如 1-2-3-4-5-6-7-8-9
第一次迭代后: 1-3-2-4-5-6-7-8-9 (e 指向 2 和 o 指向 3)
第二次迭代后: 1-3-5-2-4-6-7-8-9 (e 指向 2 和 o 指向 5)
第三次迭代后: 1-3-5-7-2-4-6-8-9 (e 指向 2 和 o 指向 7)
第四次迭代后: 1-3-5-7-9-2-4-6-8 (e 指向 2 和 o 指向 9)
希望对你有帮助:)
【讨论】:
这是我的第一个答案:P【参考方案5】:我们使用两个指针 p1 和 p2 分别跟踪奇数和偶数节点。如果列表的长度是偶数,我们在 p2.next == null 处停止,即当 p2 到达最后一个节点时。
在这种情况下,p1 位于最后一个节点的旁边。例如
1 -> 2 -> 3 -> 4 -> null
| |
p1 p2
如果列表的长度是奇数,我们停在 p2 == null,即 p1 在最后一个节点中。
1 -> 2 -> 3 -> 4 -> 5 -> null
| |
p1 p2
我们可以观察到 p1 总是比 p2 低一级,我们必须让 p1 停在奇数链表的最后一个节点,然后最终 p1.next = evenHead。也就是说,我们不能丢失奇数列表的尾部,即让 p1 = null。
解决方案:
/**
* Definition for singly-linked list.
* public class ListNode
* int val;
* ListNode next;
* ListNode()
* ListNode(int val) this.val = val;
* ListNode(int val, ListNode next) this.val = val; this.next = next;
*
*/
class Solution
public ListNode oddEvenList(ListNode head)
if (head == null || head.next == null)
return head;
ListNode odd = head;
ListNode even = head.next;
ListNode evenHead = even;
while (even != null && even.next != null)
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
odd.next = evenHead;
return head;
【讨论】:
以上是关于给定一个单链表,将所有奇数节点组合在一起,然后是偶数节点的主要内容,如果未能解决你的问题,请参考以下文章