Leetcode-92. 反转链表 II

题目

给你单链表的头指针 head 和两个整数 leftright ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

 

示例 1:

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]

示例 2:

输入:head = [5], left = 1, right = 1
输出:[5]

 

提示:

  • 链表中节点数目为 n
  • 1 <= n <= 500
  • -500 <= Node.val <= 500
  • 1 <= left <= right <= n

 

进阶: 你可以使用一趟扫描完成反转吗?

我的思路

设置一个栈,将要反转的节点放入其中,设置两个数组,将反转节点前面的节点和后面的节点分别放入其中,再用一个新链表连接这些节点。但是不知道为什么遇到下面的测试用例会出错:
[1, 2, 3]
1
1
/**
 * 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) {
        //left和right是位置而不是节点值
        Stack<ListNode> reverseNodes = new Stack<ListNode>();
        Set<ListNode> frontNodes = new HashSet<ListNode>();
        Set<ListNode> backNodes = new HashSet<ListNode>();
        int index = 1;
        ListNode reverseList = new ListNode(0);
        ListNode tempNode = head;
        ListNode preNode = head;

        while(tempNode != null){
            if(index < left){
                frontNodes.add(tempNode);
            }
            else if(index >= left && index <= right){
                reverseNodes.push(tempNode);
            }
            else{
                backNodes.add(tempNode);
            }
            tempNode = tempNode.next;
            ++index;
        }

        tempNode = reverseList;
        for(ListNode node : frontNodes){
            tempNode.next = node;
            tempNode = tempNode.next;
        }
        while(!reverseNodes.isEmpty()){
            tempNode.next = reverseNodes.pop();
            tempNode = tempNode.next;
        }
        for(ListNode node : backNodes){
            tempNode.next = node;
            tempNode = tempNode.next;
        }
        //最后一个节点要记得设置next指针为null,否则会产生环
        tempNode.next = null;
        return reverseList.next;
    }
}

输出结果:

PS:笔试时能够AC即可,即可以用空间复杂度高的方法解题,但是面试时如果用额外空间解题的话就避开了要考察的知识点,所以还是用时间复杂度低点的方法。当然要是想不出以能解题为优先考虑。

双指针-头插法

找到left的前一个节点,用一个指针记录下他,再用一个指针记录left位置的节点,然后将left到right的节点头插到left节点前面。难点在于怎么找到left前面的节点,left节点和right节点,因为需要这些节点做连接。另外一个难点是怎么进行头插连接。还要注意设置一个哨兵头节点,因为head节点也可能被反转。

我的错误代码:

/**
 * 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 {
    //找到left的前一个节点,用一个指针记录下他,再用一个指针记录left位置的节点,然后将left到right的节点头插到left节点前面
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode beforeLeft = null;
        ListNode leftNode = null;
        ListNode tempNode = head;
        int index = 1;

        //这个循环刚开始设置时也有问题,没有得到第一个要反转节点的前面的节点
        for(int i = 1; i < left-1; ++i){
            tempNode = tempNode.next;
        }
        beforeLeft = tempNode;
        leftNode = tempNode.next;
        tempNode = tempNode.next.next;
        
        ListNode currNode = tempNode;
        for(int i = 0; i < right-left; ++i){
            currNode = currNode.next;
            //不能这样连接,因为leftNode的值没有更新,每次新的节点都会连接到同一个leftNode,导致前面的节点没有成功连接,从而丢失了前面的节点
            tempNode.next = leftNode;
            tempNode = currNode;
        }
        //出来后,currNode是要反转的最后一个节点后面的节点,但是要进行连接需要最后一个要反转的节点,所以上面的循环设计就是有问题的,应该保证出来后能得到最后一个要反转的节点。
        currNode = currNode.next;
        //因为循环设置有问题,导致下面这条语句执行后会永远丢失要反转的节点
        beforeLeft.next = tempNode;
        leftNode.next = currNode;

        return head;
    }
}

正确代码:

/**
 * 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 {
    //找到left的前一个节点,用一个指针记录下他,再用一个指针记录left位置的节点,然后将left到right的节点头插到left节点前面。
    public ListNode reverseBetween(ListNode head, int left, int right) {
        //关键:设置哨兵头节点,以免head节点被反转后无法返回正确的链表
        ListNode reverseList = new ListNode(0);
        reverseList.next = head;

        /**
            以下设置错误,应该初始将beforeLeft设置为reverseList,这样才符合当left位置为head时,    left前面还有一个节点
            ListNode beforeLeft = reverseList.next;
            ListNode leftNode = reverseList.next.next;
        */
        //利用for循环找到对应的节点,注意初始时leftNode和beforeLeft的值的设置
        ListNode beforeLeft = reverseList;
        ListNode leftNode = reverseList.next;
        for(int i = 0; i < left-1; ++i){
            beforeLeft = beforeLeft.next;
            leftNode = leftNode.next;
        }

        //哨兵节点的好处就是如果要从head开始反转,也会不断更新reverseList.next的值
        ListNode tempNode = null;
        ListNode currNode = null;
        for(int i = 0; i < right-left; ++i){
            tempNode = leftNode.next;
            //关键:注意更新leftNode.next的值
            leftNode.next = leftNode.next.next;
            tempNode.next = beforeLeft.next;
            beforeLeft.next = tempNode;
        }
        
        return reverseList.next;
    }
}

截断反转法

截断要反转部分的链表,反转完后再连接回去。由于head节点可能改变,需要设置哨兵节点避免复制的分类讨论。需要记录left和right节点,以及left前面一个节点和right后面一个节点。注意利用for循环找节点。
/**
 * 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 {
    //截断要反转部分的链表,反转完后再连接回去。由于head节点可能改变,需要设置哨兵节点避免复制的分类讨论。
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode reverseList = new ListNode(0);
        ListNode preLeft = null;
        ListNode leftNode = null;
        ListNode rightNode = null;
        ListNode succRight = null;
        reverseList.next = head;

        //初始将left的前一个结点指向哨兵结点,遍历left-1次找到left前一个结点
        preLeft = reverseList;
        for(int i = 0; i < left-1; ++i){
            preLeft = preLeft.next;
        }
        leftNode = preLeft.next;

        //寻找right结点和right后一个结点
        rightNode = leftNode;
        for(int i = 0; i < right-left; ++i){
            rightNode = rightNode.next;
        }
        succRight = rightNode.next;

        //截断链表
        preLeft.next = null;
        rightNode.next = null;

        //反转链表
        reverseLinkedList(leftNode);

        //连接反转后的链表
        preLeft.next = rightNode;
        leftNode.next = succRight;

        return reverseList.next;
    }

    public ListNode reverseLinkedList(ListNode head){
        ListNode reverseList = new ListNode(0);
        reverseList.next = head;

        //初始时left节点前面一个指针为null没错
        ListNode preNode = reverseList;
        ListNode tempNode = reverseList.next;
        ListNode currNode = null;
        while(tempNode != null){
            currNode = tempNode.next;
            tempNode.next = preNode;
            preNode = tempNode;
            tempNode = currNode;
        }

        return reverseList.next;
    }
}

 学到和回忆了

//声明一个栈对象,并向内压入三个元素
Stack stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
//判断是否为空栈
System.out.println(stack.isEmpty());//输出:false
//使用peek()方法查询栈顶元素,使用pop()方法取出栈顶元素
System.out.println(stack.peek());//输出:3
System.out.println(stack.pop());//输出:3
System.out.println(stack.peek());//输出:2
System.out.println(stack.pop());//输出:2
System.out.println(stack.peek());//输出:1
System.out.println(stack.pop());//输出:1
System.out.println(stack.isEmpty());//输出:true

参考

反转链表 II - 反转链表 II - 力扣(LeetCode) (leetcode-cn.com)

Java-双指针-头插法 - 反转链表 II - 力扣(LeetCode) (leetcode-cn.com)

posted @ 2022-04-05 20:25  心空之上  阅读(10)  评论(0)    收藏  举报