链表翻转(In-place Reversal of a LinkedList)

Pattern: In-place Reversal of a LinkedList,链表翻转

介绍部分来自:https://www.zhihu.com/question/36738189/answer/908664455 作者:穷码农

在众多问题中,题目可能需要你去翻转链表中某一段的节点。通常,要求都是你得原地翻转,就是重复使用这些已经建好的节点,而不使用额外的空间。这个时候,原地翻转模式就要发挥威力了。

这种模式每次就翻转一个节点。一般需要用到多个变量,一个变量指向头结点(下图中的current),另外一个(previous)则指向咱们刚刚处理完的那个节点。在这种固定步长的方式下,你需要先将当前节点(current)指向前一个节点(previous),再移动到下一个。同时,你需要将previous总是更新到你刚刚新鲜处理完的节点,以保证正确性。

img

咱们怎么去甄别这种模式呢?

  • 如果你被问到需要去翻转链表,要求不能使用额外空间的时候

经典题目:

1、Reverse a LinkedList (easy)

35. 翻转链表关注问题

206. 反转链表

​ 反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

进阶:

你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

解题思路:pre 指向反转链表的表头。current 指向被反转链表的表头的下一个节点。 p 指向被反转的节点。

1->2->3->4->5->null
1<-2<-pre(p)  (p)current->3->4->5->null
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        // 1->2->3->4->5->null
        // 1<-2<-pre(p)  (p)current->3->4->5->null
        if (head == null)
            return null;

        ListNode p = head;  
        ListNode pre = null;
        ListNode current;
        while (p != null){
            current = p.next;       // 保存原链表的下一个节点
            p.next = pre;           // 断开的节点指向反转链表的头部
            pre = p;                // 反转链表头部等于新的增加的节点
            p = current;            // 继续进行下一个节点的反转
        }
        return pre;
    }
}

2、Reverse a Sub-list (medium)

92. 反转链表 II

​ 反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

​ 说明:
​ 1 ≤ m ≤ n ≤ 链表长度。

示例:

输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL

解题思路: 根据上一题的反转模式。

  1. 创建一个新的头结点 res,保证如果子链从第一个节点开始反转时能有上一个节点 preHead

  2. preHead 保存反转节点的上一个节点。例如 m = 1 时,上一个节点值就是头结点: (preHead)0->1->2->3->4->5->NULL

  3. preHead.next.next 为反转链表的末尾节点的 next。用于指向反转后断开的节点(cur)。

  4. preHead.next 为反转链表的末尾节点的上一个节点的 next。用于指向反转后的头节点(pre)。

  5. 最后返回 res.next 即链表头结点的下一个引用。

    3. 4. 例如本题:

在这里插入图片描述

在这里插入图片描述

class Solution {
    public ListNode reverseBetween(ListNode head, int m, int n) {
        if (m == n || head == null)
            return head;

        ListNode res = new ListNode(0);      	// 创建一个链表头节点,保证从第一个节点反转时,能有上一个节点
        res.next = head;

        ListNode preHead = res;                 // 指向被反转子链表的上一个节点 (preHead)1->2->3->3->5->NULL
        for (int i = 1; i < m; i++) {
            preHead = preHead.next;
        }

        // 反转链表操作
        ListNode p = preHead.next;
        ListNode pre = null;                    // 反转后 (pre)4->3->2->NULL
        ListNode cur = null;                    // 反转后 (cur)5->NULL
        for (int i = m; i <= n; i++) {          // 反转操作
            cur = p.next;
            p.next = pre;
            pre = p;
            p = cur;
        }

        preHead.next.next = cur;                // 反转后的链表的尾结点指向剩余节点 cur
        preHead.next = pre;                     // 反转的上一个节点指向反转的头节点 pre

        return res.next;
    }
}

3、Reverse every K-element Sub-list (medium)

25. 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/solution/kge-yi-zu-fan-zhuan-lian-biao-by-powcai/

k = 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++;
        }

        if (count == k){                    // 有足够长的子链
            cur = reverseKGroup(cur, k);    // 继续从当前节点往下探索子链
            while (count != 0){             // 反转链表
                count--;
                ListNode p = head.next;     // 记录头结点的下一个节点
                head.next = cur;            // 第一遍为递归完返回的子链表
                cur = head;                 // 往前挪一个节点
                head = p;                   // 继续从下一个节点开始
            }
            head = cur;
        }

        return head;
    }
}

K = 3 非递归尾插法思路:

在这里插入图片描述

注释我感觉我是乱写的,看图和代码比较清晰…

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || k == 0)
            return head;

        ListNode res = new ListNode(0);    // 新建表头,指向 head
        res.next = head;

        ListNode preHead = res;     // 从表头开始,定位子链头部
        ListNode tail = res;        // 从表头开始,定位子链尾部

        while (true){               // 一直循环,直到 tail 定位到尾部
            int count = 0;
            while (tail != null && count != k){     // 尾部不为空 且 数量为 k
                count++;
                tail = tail.next;
            }
            if (tail == null) break;                // 尾部为空则不进行翻转

            // 尾插法
            ListNode tempHead = preHead.next;       // 记录反转后的子链表的头部节点
            while (preHead.next != tail){           // 遍历至 定位的尾指针 的前一个节点
                ListNode cur = preHead.next;        // 从头开始,遍历节点
                preHead.next = cur.next;            // 定位到下一个节点
                cur.next = tail.next;               //
                tail.next = cur;                    // 尾插
            }
            preHead = tempHead;                     // 从子链表的尾部继续反转
            tail = tempHead;                        // 从子链表的尾部继续探索
        }
        return res.next;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值