算法通关村第二关——链表反转笔记(白银挑战)

算法通关村第二关——链表反转笔记(白银挑战)

    • leetcode 92. 反转链表 II
      • 方法1:自己想的方法
      • 方法2:穿针引线法
      • 方法3:头插法
      • 方法4:递归法(离大谱)

leetcode 92. 反转链表 II

leetcode 92. 反转链表 II

反转两个数区间的结点

方法1:自己想的方法

思路:

反转节点最重要的是找到结点的前一个节点,那么这题就需要找到left的前一个节点

然后翻转后要记录到后一个节点的下一个节点,这个记录

翻转后,再拼接

第一步: 找到left位置的节点

image-20230721094931696

通过定义一个虚拟节点,解决边界问题,例如:只有[1,3],那么1的前节点就是空,所以定义一个虚拟节点的值为空即可

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if (head == null || left == right) {
            return head;
        }

        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        // 找到left的前一个节点
        ListNode prevLeft = dummy;
        for (int i = 1; i < left; i++) {
            prevLeft = prevLeft.next;
        }
        ListNode leftNode = prevLeft.next;
    }
}

第二步: 找到区间

算法通关村第二关——链表反转笔记(白银挑战)_第1张图片

那么需要反转的区间就是中间这一块,所以定义prev和curr来进行反转,跟反转链表1的题目一样

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if (head == null || left == right) {
            return head;
        }

        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        // 找到left的前一个节点
        ListNode prevLeft = dummy;
        for (int i = 1; i < left; i++) {
            prevLeft = prevLeft.next;
        }
        ListNode leftNode = prevLeft.next;

        // 反转[left, right]之间的节点
        ListNode curr = leftNode;
        ListNode prev = null;
        for (int i = left; i <= right; i++) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
    }
}

第三步: 将这段反转的链表拼接上去

image-20230721100011586

// 将反转部分与原链表连接起来
prevLeft.next = prev;
leftNode.next = curr;

这样就反转完毕啦~

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        if (head == null || left == right) {
            return head;
        }

        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        // 找到left的前一个节点
        ListNode prevLeft = dummy;
        for (int i = 1; i < left; i++) {
            prevLeft = prevLeft.next;
        }
        ListNode leftNode = prevLeft.next;

        // 反转[left, right]之间的节点
        ListNode curr = leftNode;
        ListNode prev = null;
        for (int i = left; i <= right; i++) {
            ListNode nextTemp = curr.next;
            curr.next = prev;
            prev = curr;
            curr = nextTemp;
        }
        
        // 将反转部分与原链表连接起来
        prevLeft.next = prev;
        leftNode.next = curr;

        return dummy.next;

    }
}

方法2:穿针引线法

其实我的方法跟这个方法一样,只是获取区间的方法不一样

/**
     * 方法1:穿针引线法
     *
     * @param head
     * @param left
     * @param right
     * @return
     */
    public static ListNode reverseBetween(ListNode head, int left, int right) {
        // 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
        ListNode dummyNode = new ListNode(-1);
        dummyNode.next = head;

        ListNode pre = dummyNode;
        // 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
        // 建议写在 for 循环里,语义清晰
        for (int i = 0; i < left - 1; i++) {
            pre = pre.next;
        }

        // 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
        ListNode rightNode = pre;
        for (int i = 0; i < right - left + 1; i++) {
            rightNode = rightNode.next;
        }

        // 第 3 步:切断出一个子链表(截取链表)
        ListNode leftNode = pre.next;
        ListNode succ = rightNode.next;

        // 注意:切断链接
        pre.next = null;
        rightNode.next = null;

        // 第 4 步:同第 206 题,反转链表的子区间
        reverseList(leftNode);

        // 第 5 步:接回到原来的链表中
        pre.next = rightNode;
        leftNode.next = succ;
        return dummyNode.next;
    }

    /**
     * 基本的反转方法
     *
     * @param head
     * @return
     */
    public static ListNode  reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }

这里反转方法还可以使用递归的方式:

private void reverseLinkedList(ListNode head) {
    // 也可以使用递归反转一个链表
    ListNode pre = null;
    ListNode cur = head;
    while (cur != null) {
        ListNode next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
}

方法3:头插法

这个方式其实就是在前面两种方式的切断那块代码不进行切断,直接转

public static ListNode reverseBetween2(ListNode head, int left, int right) {
  // 设置 dummyNode 是这一类问题的一般做法
  ListNode dummyNode = new ListNode(-1);
  dummyNode.next = head;
  // 前一个节点的指针
  ListNode pre = dummyNode; 
  // 找到left节点的前一个节点
  for (int i = 0; i < left - 1; i++) { 
    pre = pre.next;
  }
  // 当前节点的指针
  ListNode cur = pre.next; 
  ListNode next;
  // 反转left到right之间的节点
  for (int i = 0; i < right - left; i++) { 
    // 保存当前节点的下一个节点
    next = cur.next; 
    // 将当前节点的next指针指向next节点的下一个节点,完成节点交换
    cur.next = next.next; 
    // 将next节点插入到pre节点的后面
    next.next = pre.next; 
    // 将pre节点的next指针指向next节点,即完成插入操作
    pre.next = next; 
  }
  return dummyNode.next; // 返回反转后的链表头节点
}

主要是把拼接的步骤提前做好就行了

方法4:递归法(离大谱)

递归也可以使用这种方式,那么判断就尤为重要了

class Solution {
    int i = 0; // 计数器i,用于记录当前遍历到的节点位置

    public ListNode reverseBetween(ListNode head, int left, int right) {
        i++; // 每次递归进入函数时,计数器i自增1
        // 如果i等于right,说明已经递归到了right位置,直接返回head
        if (i == right) { 
            return head;
        }
        // 如果i小于left,说明还没有到达left位置,继续递归,处理下一个节点
        if (i < left) { 
            head.next = reverseBetween(head.next, left, right);
            return head;
        // 若i大于等于left,说明已经到达left位置,开始反转链表
        } else { 
            ListNode node = reverseBetween(head.next, left, right); // 递归处理下一个节点
            ListNode nex = head.next.next; // 保存head的下两个节点的引用
            head.next.next = head; // 将head的下一个节点的next指针指向head,完成反转
            head.next = nex; // 将head的next指针指向原来的下两个节点,保持链表连接
            return node; // 返回递归处理后的链表头节点
        }
    }
}

递归看看就好,有点离大谱,干不动

你可能感兴趣的:(算法,算法,链表,笔记,java,数据结构)