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

时间:2016-03-12 19:05:24

标签: java c++ algorithm linked-list

我已经解决了这个问题:

  

给定单链表,将所有奇数节点组合在一起,然后是偶数节点。请注意,这里我们讨论的是节点编号,而不是节点中的值。

     

你应该尝试到位。该程序应该以O(1)空间复杂度和O(节点)时间复杂度运行。

     

示例:

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;
    }

5 个答案:

答案 0 :(得分:4)

假设你从一个单链表开始。它通常具有头部和尾部指针(这里左右倾斜),每个链接指向下一个。

在下图中,偶数节点为蓝色,奇数节点为灰色。

enter image description here

解决这个问题的一种方法是保持6个指针:

  • 指向您当前在原始列表中处理的链接的指针,以及指向原始列表尾部的指针。
  • 指向您已经处理过的偶数节点的头部和尾部的指针。
  • 指向已经处理过的奇数节点的头尾的指针。

(这仍然是 O(1)。)

在上图中,原始列表(左侧的内容)位于右侧。处理后的偶数节点列表位于左上角,处理后的奇数节点列表位于左下角。

enter image description here

现在,虽然原始列表中仍有未处理的节点,但根据需要将当前节点移动到偶数或奇数列表。最后,只需将一个点的尾部链接到另一个的头部节点即可。将原始列表的头部和尾部指针设置为此结果列表,您就完成了。

答案 1 :(得分:4)

你已经得到了很好而详细的答案,所以我觉得有点愚蠢的发布这个,但是我需要一些时间来制作图纸,所以无论如何它都在这里。

我将解释您发布的算法,因为它可以直接在C ++中翻译(我以前虽然节点的"奇怪"是由它的值而不是它的位置决定的,并且需要使用指针指针来修改头部。

我只是在视觉上解释这个算法,我不会长篇大论如何实际上只是一个"压缩"有两个链表(一个用于奇数元素和偶数元素)以及如何处理指针的方式 正如评论中所述,有专门的书籍。

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;

Pointers initialization

周期的第一次迭代

lastOdd.next = lastEven.next;

First iteration, I1

lastOdd = lastOdd.next;

First iteration, I2

lastEven.next = lastOdd.next;

First iteration, I3

lastEven = lastEven.next;

First iteration, I4

所有其他迭代

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

All remaining iterations

一旦周期结束,唯一剩下的就是加入两个列表

Lists joined

答案 2 :(得分:2)

与其编写代码,我只是简单地给出一个基本概念的简要概要,希望它应该足够简单易懂。如果你开始绘制各种图表,我认为你可以在这里容易混淆。当基本概念非常简单时,没有必要在这里绘制复杂的图表

单链表中的尾指针

让我们定义一个“尾指针”是什么。尾指针是指向链表末尾的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

答案 3 :(得分:2)

想法是将奇数节点收集在一起(开始时)同时跟踪奇数和偶数指针。

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)

第3次迭代后: 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)

我希望它有所帮助:)

答案 4 :(得分:0)

我们使用两个指针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低1步,我们必须让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;
    }
}