反转链表 & 反转链表II【M】

92. 反转链表 II

很显然,对于反转链表的题来说,会有递归和迭代两种解法。

如果用递归,建议先熟悉下面的普通反转链表问题,一步步推导到指定范围内的链表反转问题:


迭代法:
这题,我的做法可行,但是很绕,很容易出现紧张就理不清楚思路的问题:

实现了:一次遍历,完成翻转部分链表的结果。

整体思路就是:从头开始找到要反转的起始结点,并保存交界处的两个结点;不断反转到需要反转的终点,保存好交界处的两个结点,最后交叉相连即可
——注意:一定要在纸上画出流程,不然肯定做不出来的

链表: 前头 – 前尾 --> 中头 – 中尾 -> 后头 – 后尾

反转之后: 前头 – 前尾 --> 中尾 – 中头 -> 后头 – 后尾

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if(left == right) return head;
        int count = 0;
        ListNode dummy = new ListNode(0, head);
        // unchange表示未修改的部分的尾巴,tail是指翻转之后的尾巴结点(未反转前的反转起点),
        // cur、prev和next是在翻转过程中用到的
        ListNode unchange = dummy, tail = dummy, cur, prev, next;
        while(count < left){
            unchange = tail;
            tail = tail.next;
            count++;
        }
        // 这边找到了要反转的起点:tail就是反转的起点(反转结束之后就是尾巴)
        // unchange就是前面未反转的尾巴(用来最后连接用的)
        cur = tail;
        prev = null;
        while(count <= right){
            next = cur.next;
            cur.next = prev;
            prev = cur;
            cur = next;
            count++;
        }
        // 要反转的部分全部做了反转,此时prev指向的是反转的尾巴(反转之后的首部),cur/next指向的是最后未反转的头部(用来连接用)
        unchange.next = prev;		// 前尾--中尾
        tail.next = cur;			// 中头 -- 后头
        return dummy.next;
    }
}

——为了防止从头开始反转,所以设置了dummy结点,用来保证反转之后也能找到头结点

递归方法:再下面的基础上可以自然的推出来:

可以将其看成:

——先获取前面不需要反转的部分,然后将后面要反转的部分当成是从头开始反转部分链表问题

class Solution {
    // 只是用来做反转的,从头开始反转部分的解法
    private ListNode recurive(ListNode head, int N){
        if(N == 1) return head;
        ListNode res = recurive(head.next, N - 1);
        ListNode temp = head.next.next;
        head.next.next = head;
        head.next = temp;
        return res;
    }
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if(left == right) return head;
        ListNode cur = head, prev = head;
        int count = 1;
        if(left == 1) return recurive(head, right);		// 如果left=1,就是从头开始反转
        while(count < left){
            prev = cur;
            cur = cur.next;
            count++;
        }
        // 到这边,就找到了后面需要反转的起始结点,prev指向的是未反转的尾结点;cur=next指向的是反转的头结点
        prev.next = recurive(cur, right - left + 1);
        return head;
    }
}

还可以再优化:

class Solution {
    // 只是用来做反转的
    private ListNode recurive(ListNode head, int N){
        if(N == 1) return head;
        ListNode res = recurive(head.next, N - 1);
        ListNode temp = head.next.next;
        head.next.next = head;
        head.next = temp;
        return res;
    }
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if(left == 1) return recurive(head, right);
        else head.next = reverseBetween(head.next, left - 1, right - 1);	// 核心,不断地推进到要反转地起点
        return head;
    }
}

基础:206. 反转链表 & 剑指 Offer 24. 反转链表

最常规题,一般这种就要求直接写出来,连面试题中都不会出现的(出现,应该也是考察多解法的)

迭代法:

——需要有3个指针,prev、cur、next,prev初始为null,它们分别保存当前结点的前一个、当前结点、当前结点的后一个

  • 当前结点的prev,就是反转之后,当前结点的next
  • 当前结点
  • 当前结点的next,反转之后,就是当前结点的prev,因为当前结点的next已经断掉,所以为了能成功找到next结点,需要提前保存
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null) return head;
        ListNode prev = null, cur = head, next;
        while(cur != null){
            next = cur.next;
            cur.next = prev;
            prev = cur;
            cur = next;
        }
        return prev;
    }
}

——具体看代码,且要在纸上画出来。(反正,毛想想我是想不出来的)

稍微有难度的是,递归解法,也需要牢记的。

递归需要记住的是:当前结点在反转的时候,其后面的结点都已经反转完了,递归是递归到结点尾部,然后再反转:

由于递归到结点尾了:1->2->3->4->5->null:

  • 此时head在5,5就是未来的头结点,且5->next = null,选择直接返回
  • 此时head在4,收到了返回值res为5,此时4->next=5,所以需要将5->next=4,那么5已经反转完成,而为了格式统一4->next=null

——总结,递归就是从链表尾部开始反转到,到某个结点时,其后面的结点已经完成了反转

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head== null || head.next == null) return head;		// 处理特殊情况,也是递归的终止条件
        ListNode res = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return res;			// 需要保存新的链表头,就是原来的尾部
    }
}

ps:扩展

只反转前面n个结点,即 1->2->3->4->5->null,反转前面4个 => 4->3->2->1->5->null

可以发现,该题目是兼容上面的反转整个链表的。

只考虑递归的方法,如果直接想可能解不出来,需要先考虑到完全反转的递归做法,然后再此基础上想递归做法:带上计数器

思路也是递归到反转的尾部,然后

class Solution {
    public ListNode reverseBetween(ListNode head, int N) {
        if(N == 1) return head;
        ListNode res = reverseBetween(head.next, N-1);
        ListNode temp = head.next.next;		// 保留当前结点的后继的后继(维护的是未反转的头结点)
        head.next.next = head;		// 当前结点的后继结点的next指向当前结点
        head.next = temp;		// 当前结点更新其next结点
        return res;
    }
}

pps:迭代的方法:

class Solution {
    public ListNode reverseBetween(ListNode head, int N) {
    if(left == 1) return head;			// 边界条件
    ListNode prev = null, cur = head, next;
    int count = 1;
    while(count <= left){
        next = cur.next;
        cur.next = prev;
        prev = cur;
        cur = next;
        count++;
    }
    head.next = cur;		// 此时的head就是反转后的尾巴,此时的cur/next就是未翻转的首部
    return prev;			// 此时的prev就是反转后的头
}

参考:https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/bu-bu-chai-jie-ru-he-di-gui-di-fan-zhuan-lian-biao/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值