给定一个单链表,将所有奇数节点组合在一起,然后是偶数节点

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;

所有其他迭代

正如您现在可以想象的那样,循环重复,而 lastEvenlastOdd 都有下一个元素,因为前者是后者之前的一个元素,这意味着有 列表中至少有两个元素未处理

一旦循环结束,剩下的就是加入两个列表

【讨论】:

写得很好的答案。荣誉。【参考方案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;
    

【讨论】:

以上是关于给定一个单链表,将所有奇数节点组合在一起,然后是偶数节点的主要内容,如果未能解决你的问题,请参考以下文章

328. 奇偶链表

LeetCode 0328. 奇偶链表

题目地址(328. 奇偶链表)

算法系列——链表的奇偶重排

算法系列——链表的奇偶重排

算法系列——链表的奇偶重排