Leetcode-Pointer Magic

来源:互联网 发布:2017淘宝违禁词有哪些 编辑:程序博客网 时间:2024/06/10 21:00

  • Problem Description
    • Reverse Nodes in k-Group
  • Analysis
    • Knowledge Needed
    • Algorithm Analysis
    • Complexity Analysis
      • Time complexity
      • Space complexity
  • code
  • Thoughts

Problem Description

Reverse Nodes in k-Group

Given a linked list, reverse the nodes of a linked list k at a time and return its modified list.

k is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is.

You may not alter the values in the nodes, only nodes itself may be changed.

Only constant memory is allowed.

For example,
Given this linked list: 1->2->3->4->5

For k = 2, you should return: 2->1->4->3->5

For k = 3, you should return: 3->2->1->4->5
具体参见Leetcode

Analysis

Knowledge Needed

  • 这道题从题目上就可以看出是对指针和链表内容的考查,主要是实现链表的反转。题目在这个基础的考查点上通过给出数字k的分段反转增加难度。需要注意的点是解读好题目要求的反转方式——对于每k个节点进行反转,然后不足k个的不做反转处理。

  • 额外的要求是 Only constant memory is allowed.
    这意味着不能用一个数组把所有节点存储起来再重新连接,也不能把节点复制一遍再进行反转连接

-对于题目中的k is a positive integer and is less than or equal to the length of the linked list.我觉得不知道是我理解有问题,还是题目给错了,还是题目的测试程序有问题。幸亏LeetCode还是给出了分析报告。
Wrong report
如果我没有理解错的话,k应该满足k<=1的条件才对。但是这里是k=2。

Algorithm Analysis

下面用一个例子来分析这道题。首先我们来假设一个一般性的情况。

Suppose Link List is 1->2->3->4->5->6->7->8->NULL
这里写图片描述

当k=3时,Expected Output should be 3->2->1->6->5->4->7->8->NULL

这里写图片描述

思路:
1.对于这些节点,分成三组,除了第三组不足三个外不需要反转外,其余的两组均需组内节点反转连接。
2.因为题目中明确要求是只能使用constant memory,所以我们只能利用链表的最大特征——指针来实现我们的链表反转。整个过程就只能通过指针的变换来实现,其实就是——pointer magic。
3.简单的拿两个节点的反转来说,要满足的条件:

  • 被反转节点的next指针指向的节点要记录下来要不然会丢失
  • 头指针要更改成新的节点
  • 原来的头结点要指向储存好的 被反转节点的next指针指向的节点

这里写图片描述

所以,我们可以借助一些指针变量来存储这些指针之间的关系。

这里写图片描述

以上图为例,head节点有prev指针指向,cur指示着当前需要反转的指针指向的节点,post则是指向cur的下一节点。即:

prev = head;cur = prev->next;post = cur->next;

然后通过这些变量之间的变换:

cur->next = prev;prev->next = post;head = cur;cur = post;if (post != NULL) post = post->next;

这里写图片描述

现在已经完成第一个节点和第二个节点之间的反转。

4.两个节点之间的反转还是比较容易的,关键是三个以上的节点该如何进行反转。
上文的那个例子,对第一段的反转已经完成了一半,关键在于第三个节点现在怎么反转到最前面去。
在这里,我们其实可以运用整体性的观念,把已经处理好的第一个和第二个节点看作是一个整体,看作是一个节点,然后和第三个节点进行前面已经进行过的两个节点的反转一样。

cur->next = head; //only different from two-node reverseprev->next = post;head = cur;cur = post;if (post != NULL) post = post->next;

上面两个节点的

cur->next = prev;

换成

cur->next = head;

也没有影响

操作后结果如图:

这里写图片描述

5.接下来就是没断没断之间的连接的问题。因为链表是一个顺序结构,所以在处理完下一段之前我们还是不知道下一段的head在哪里,因为我们没有办法提前遍历到那里,只能逐个节点的处理,所以,我们可以用一个指针last把当前这一段的最后一个节点也就是prev指向的对象存储起来,等到下一个段最后的head产生后,也就是反转完成后,使得last->next = head,实现两段之间的连接。
这里写图片描述

6.最后一个问题就是如何实现如何不足k个就不反转。
此处先遍历整个链表一次,得到链表节点的个数,然后通过计数器和节点个数之间的关系来判断。但是有一点需要注意的是,这种算法的处理中,每一段的处理是从当段的第二个节点处开始的,也就是说,在两段过度的时候,计数器要多跳一次。
这里写图片描述

Complexity Analysis

Time complexity

Time complexity of this algorithm is O(n).We look over the linked list for 2 times, respectively.So the time complexity only depends on the length of the list, which is O(n).

Space complexity

Space complexity is O(1),since we only use several pointers like last, cur,so our space complexity fits the test.

code

/** * Definition for singly-linked list. * struct ListNode { *     int val; *     ListNode *next; *     ListNode(int x) : val(x), next(NULL) {} * }; */class Solution {public:    ListNode* reverseKGroup(ListNode* head, int k) {        int count = 1, sum = 0;        ListNode* prev = NULL, *cur = NULL, *post = NULL, *last = NULL, *result = NULL;        prev = head;        if (k == 1) return head;        if (head != NULL) {            if (head->next != NULL) {                cur = head->next;                if (cur->next == NULL) {                    if (k == 2) {                        cur->next = head;                        prev->next = post;                        head = cur;                    }                    return head;                } else {                    post = cur->next;                    ListNode* temp = head;                    while (temp != NULL) {                        sum++;                        temp = temp->next;                    }                    if (sum < k) {                        return head;                    }                    while(true) {                        if (count % k == 0) {                            if (count / k == 1) result = head;                            if (last != NULL) last->next = head;                            last = prev;                            if (sum - count < k) {                                break;                            } else {                                head = prev = cur;                                cur = head->next;                                post = cur->next;                                count++;                            }                        }                        if (cur == NULL) {                            if (last != NULL) last->next = head;                            last = prev;                            break;                        }                        cur->next = head;                        prev->next = post;                        head = cur;                        cur = post;                        if (post != NULL) post = post->next;                        count++;                    }                }            } else {                return head;            }        }        return result;    }};

Thoughts

其实整道题的脉络还是很清晰的就是利用指针之间的关系对链表这个顺序存储的结构进行倒置,关键在于想清楚,从哪里开始reverse,是从后向前,还是从前向后,要利用几个指针变量来实现反转,那些节点可能会丢失,可能会丢失就找个变量存起来,还有就是整体的看待一个问题和怎么样处理每k个节点一段这些问题。把这些问题想清楚,算法的雏形大概也就出来了。剩下的都是一些细节问题,such as memory error & memory overflow,小心处理就好。

原创粉丝点击