Skip to content

Latest commit

 

History

History
163 lines (163 loc) · 6.35 KB

25.K个一组翻转链表(困难).md

File metadata and controls

163 lines (163 loc) · 6.35 KB
题目: 给你一个链表,每k个节点一组进行翻转,请你返回翻转后的链表;
    k是一个正整数,它的值小于或等于链表的长度;
    如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序;
    给你这个链表:1->2->3->4->5
        当 k=2 时,应当返回: 2->1->4->3->5
        当 k=3 时,应当返回: 3->2->1->4->5
        你的算法只能使用常数的额外空间。
        你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换
1.栈: 
    [1]思路: 遍历链表,每遍历到一个添加到栈中,
        如果添加的计数不足k个则直接指向剩余的,如果计数为k则从栈中取节点添加到反转的链表中;
    [2]实现: 
        class Solution {
            public ListNode reverseKGroup(ListNode head, int k) {
                // 定义一个双端队列对象作为"栈"
                Deque<ListNode> stack = new ArrayDeque<>();
                // 定义一个"假"节点,用来返回反转后的链表
                ListNode dummy = new ListNode(0);
                // 定义一个用来遍历"假"节点的节点
                ListNode p = dummy;
                // 遍历给定的链表
                while (true){
                    // 定义一个计数变量
                    int count = 0;
                    // 定义一个用来遍历给定链表的节点
                    ListNode temp = head;
                    // 遍历k个节点,添加到"栈"中
                    while (temp != null && count < 3){
                        stack.add(temp);
                        temp = temp.next;
                        count++;
                    }
                    // 判断是否够k个节点,不够k个则直接接上每次遍历的头结点
                    if(count != k){
                        p.next = head;
                        break;
                    }
                    // 如果stack中不为空(即满k个节点)则需要反转
                    while (!stack.isEmpty()){
                        p.next = stack.pollLast();
                        p = p.next;
                    }
                    // 节点后移(遍历的和添加的都需要)
                    p.next = temp;
                    head = temp;
                }
                return dummy.next;
            }
        }
        class ListNode {
            int val;
            ListNode next;
            ListNode(int x) { val = x; }
            public int getVal() { return val; }
            public void setVal(int val) { this.val = val; }
        }
    [3]复杂度分析:
        (1)时间复杂度: O(n)
        (2)空间复杂度: O(k),k为每组节点的个数;
2.尾插法:
    [1]思路:
        (1)创建一个节点,其下个节点指向给定节点,再定义两个节点引用指向创建的节点;
        (2)遍历给定节点,每遍历完一组(k个),pre指向一组节点中头节点的前一个节点,tail指向尾节点;
        (3)判断tail是否为空,为空说明遍历的个数不足k个,无需反转,直接返回;
        (4)否则使用尾插法:每次取当前组中的第一个插入到尾部,知道pre.next等于tail(相等说明该组反转完成);
        (5)将pre和tail重置位置;
    [2]实现:
        class Solution {
            public ListNode reverseKGroup(ListNode head, int k) {
                // 反转链表的头结点
                ListNode dummy = new ListNode(0);
                // 反转链表的头结点指向给定节点
                 dummy.next = head;
                // 定义两个节点指向一组节点中头节点的前一个节点和尾节点
                ListNode pre = dummy;
                ListNode tail = dummy;
                // 遍历给定节点
                while (true) {
                    // 计数变量
                    int count = 0;
                    // 移动tail到当前组的尾节点
                    while (tail != null && count != k) {
                        count++;
                        tail = tail.next;
                    }
                    // 如果为null则说明不满k个,无需反转,直接退出循环
                    if (tail == null) {break;}
                    // 先确定一组里的头结点,用来确定下一组的开始位置
                    ListNode head1 = pre.next;
                    // 不断的取一组里的头结点插入到尾节点之后
                    while (pre.next != tail) {
                        // 前两步的作用是取出当前遍历到的节点
                        ListNode cur = pre.next;
                        pre.next = cur.next;
                        // 这两步是尾插法的精髓,请仔细想明白
                        cur.next = tail.next;
                        tail.next = cur;
                    }
                    // 重置到下一组的头结点的前一个节点和尾节点
                    pre = head1;
                    tail = head1;
                }
                return dummy.next;
            }
        }
        class ListNode {
            int val;
            ListNode next;
            ListNode(int x) { val = x; }
            public int getVal() { return val; }
            public void setVal(int val) { this.val = val; }
        }
    [3]复杂度分析:
        (1)时间复杂度: O(n)
        (2)空间复杂度: O(1)
3.递归:
    [1]思路: 参考代码注释
    [2]实现:
        class Solution {
            public ListNode reverseKGroup(ListNode head, int k) {
                // 使用一个引用指向给定的节点,用于遍历
                ListNode cur = head;
                // 定义一个计数变量
                int count = 0;
                // 遍历给定的节点获取一组
                while (cur != null && count != k) {
                    cur = cur.next;
                    count++;
                }
                // 如果count等于k,说明够一组
                if (count == k) {
                    // 获取下一组的头结点(不断递归,不断卡在这里,直至不满k个)
                    cur = reverseKGroup(cur, k);
                    // 正式开始反转(这里比较难理解,可通过打断点看每步执行后的结果)
                    while (count != 0) {
                        count--;
                        // [1]用临时节点保存当前组的第二个节点
                        ListNode tmp = head.next;
                        // [2]将下一组的头结点作为当前组头结点的下一个节点
                        head.next = cur;
                        // [2]使用cur节点保存每次遍历后的反转链表的头结点
                        cur = head;
                        // [1] 每遍历一次去掉一个头结点,该头结点放在cur的头部
                        head = tmp;
                    }
                    // 把反转完的链表的头结点赋给原链表头
                    head = cur;
                }
                return head;
            }
        }
        class ListNode {
            int val;
            ListNode next;
            ListNode(int x) { val = x; }
            public int getVal() { return val; }
            public void setVal(int val) { this.val = val; }
        }
    [3]复杂度分析:
        (1)时间复杂度: O(n)
        (2)空间复杂度: O(1)