Leetcode-Pointer Magic

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 reverse
prev->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,小心处理就好。

LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文件编码格式:首先,我们可以检查所编辑的文件的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor中,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor中,可以在编辑器中选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值