【LeetCode-链表】K个一组反转链表

题目描述

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:

给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5

题目链接: https://leetcode-cn.com/problems/reverse-nodes-in-k-group/

思路1

对链表每 k 个节点执行一次普通的链表反转,过程如下图

图来自这里

具体代码如下:

/**
 * 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) {
        if(head==nullptr || k<2) return head;

        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* preTail = dummy;  // 前一段链表的链表尾
        ListNode* curTail = dummy;  // 当前这一段反转前的链表尾,反转后的链表头
        while(curTail->next!=nullptr){
            int cnt = 0;
            while(cnt<k && curTail!=nullptr){
                curTail = curTail->next;
                cnt++;
            }
            if(curTail==nullptr) return dummy->next;

            ListNode* curHead = preTail->next; // 当前这一段反转前的链表头,反转后的链表尾
            ListNode* next = curTail->next;
            curTail->next = nullptr;  // 别忘了这一句
            preTail->next = reverse(curHead);
            curHead->next = next;
            preTail = curHead;
            curTail = curHead;
        }
        return dummy->next;
    }

    /*单链表反转*/
    ListNode* reverse(ListNode* head){
        if(head==nullptr) return head;

        ListNode* pre = nullptr;
        ListNode* cur = head;
        ListNode* next = nullptr;
        while(cur!=nullptr){
            next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

思路2

思路 2 是我最初的做法,和思路 1 大致相似,但是写的不太好理解,推荐思路 1 或者思路 3.

根据最基础的链表反转来做。因为题目要求如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序,所以先求要翻转几段链表(链表长度/k),然后分别对每段子链表应用链表翻转,需要注意的是反转后指针的变化,要防止链表断裂,具体代码如下:

/**
 * 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) {
        if(head==nullptr || head->next==nullptr || k==1){
            return head;
        }

        ListNode* newHead = new ListNode(0);    // 新的链表头
        newHead->next = head;
        ListNode* pre = newHead;    // 存储前一段链表反转后的尾结点,用于连接各段链表,防止链表断裂
        ListNode* preNode = nullptr;    // 用于反转链表节点
        ListNode* curNode = head;
        ListNode* nextNode = nullptr;
        int length = getListLength(head);   // 获取链表长度
        int loop = length/k;    // 要反转loop段长为k的链表
        while(loop){
            ListNode* firstNode = curNode;  // 链表反转, firstNode记录链表反转前的第一个节点(反转后最后一个节点,用于连接链表段,防止链表断裂)
            for(int i=1; i<=k && curNode!=nullptr; i++){
                nextNode = curNode->next;
                curNode->next = preNode;
                preNode = curNode;
                curNode = nextNode;
            }

            pre->next = preNode;  //将前一段反转后链表的链表尾和当前这一段相连
            firstNode->next = curNode;  // 将当前这一段的反转后的链表尾指向下一段的未反转的链表头
            pre = firstNode;    // 更新pre为反转后链表的尾结点
            loop--;   
        }
        return newHead->next;
    }

    int getListLength(ListNode* head){
        int length = 0;
        while(head!=nullptr){
            head = head->next;
            length++;
        }
        return length;
    }
};
  • 时间复杂度:O(n)
    遍历链表一遍。
  • 空间复杂度: O(1)

思路3

使用栈来做。将节点入栈后出栈再连接就是反序了。用栈也要注意,最后一段链表的节点个数可能不够 k 个,这个时候就不需要出栈反转了,只需要记录下前一段链表的链表尾 preTail 和当前段链表的链表头 partHead,然后 preTail->next = partHead 即可。

/**
 * 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) {
        if(head==nullptr || k<2) return head;

        stack<ListNode*> s;
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* cur = head;
        ListNode* partHead;  // 当前这一段反转前的链表头
        ListNode* preTail = dummy;  // 前一段的链表尾
        while(cur!=nullptr){
            int cnt = 0;
            partHead = cur;
            while(cnt<k && cur!=nullptr){
                s.push(cur);
                cur = cur->next;
                cnt++;
            }

            if(s.size()==k){
                while(!s.empty()){
                    ListNode* node = s.top(); s.pop();
                    preTail->next = node;
                    preTail = preTail->next;
                    node->next = nullptr;  // 别忘了这一句
                }
            }else preTail->next = partHead;  // 最后一段不够 k 个,直接连接
        }
        return dummy->next;
    }
};

总结

和链表反转的题目都可以根据最基础的链表反转这题的思想来做。代码如下:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head==nullptr || head->next==nullptr){
            return head;
        }

        ListNode* preNode = nullptr;
        ListNode* curNode = head;
        ListNode* nextNode = nullptr;
        while(curNode!=nullptr){
            nextNode = curNode->next;
            curNode->next = preNode; 
            preNode = curNode;
            curNode = nextNode;
        }
        return preNode; // 注意返回的是preNode,因为curNode此时为nullptr
    }
};

这样反转链表后有两个非常有用的性质:1、反转结束后preNode就是反转后链表的头结点;2、反转结束后curNode是反转结束后preNode原先指向的那个节点,。(如果把整个链表分成多段分别翻转,这个性质就非常有用,比如当前的这一题“K个一组反转链表”),如下图

相关题目

1、反转链表:https://leetcode-cn.com/problems/reverse-linked-list/题解
2、反转链表II:https://leetcode-cn.com/problems/reverse-linked-list-ii/题解

参考

1、https://leetcode-cn.com/problems/reverse-nodes-in-k-group/solution/tu-jie-kge-yi-zu-fan-zhuan-lian-biao-by-user7208t/
2、https://leetcode-cn.com/problems/reverse-nodes-in-k-group/solution/kge-yi-zu-fan-zhuan-lian-biao-by-powcai/

posted @ 2020-04-11 20:51  Flix  阅读(751)  评论(0编辑  收藏  举报