[链表]–反转链表及进阶
题目链接
类似题型 相同题型
题目
反转一个单链表。
示例
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
解析
基础反转的还是比较简单的。
直接双指针,一个遍历反转节点。一个是该节点的前驱节点,把该节点的指针域 next 指向前驱节点j就 Okay 了,需要注意的只有一点,每次反转之后这个节点的 next 都是改变了的,所以? 需要将反转之前这个节点的 next 暂时保存起来,等到反转以后可以让它知道自己在哪,下一个要去遍历哪
代码实现
public class Solution206 {
/**
* Definition for singly-linked list
*/
class ListNode {
int val;
ListNode next;
public ListNode(int val) {
this.val = val;
}
}
/**
* 1.判断链表是否为空
* 2.prev 标记 cur 的前驱结点
* 3.curNext 指向原链表 cur 的 next 结点
* 4.遍历反转链表直到 cur = null
* 5.此时, prev 就是新的头结点
*/
public ListNode reverseList(ListNode head) {
if (head == null) return null;
ListNode cur = head;
// 标记 cur 结点的前驱结点, 也是反转链表后的 cur.next
ListNode prev = null;
while (cur != null) {
// 指向未反转前的 cur.next,
// 在 cur 发生反转后, next 会发生改变,所以需要一个结点来标记它原本的 next 结点
ListNode curNext = cur.next;
// 反转
cur.next = prev;
prev = cur;
// 按照原本的连接遍历
cur = curNext;
}
// 此时 cur 结点为 null, prev 就是原本链表的最后一个结点, 反转链表的头结点
return prev;
}
}
进阶
题目链接
题目
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
1 ≤ m ≤ n ≤ 链表长度。
示例
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
解析
进阶麻烦了,有点绕还一趟扫描完成。不过万变不离其宗,不管反转哪最后都要把反转部分后一个的 next 指向前一个
首先双指针没跑了,用起来很上手的,太舒服。
- 定义双指针 cur 和 prev,通过这名就看得出来 cur 是当前反转节点,prev 是反转节点的前驱节点。开始的时候 cur当然是 head 了,prev…null 吧,cur 通过遍历向后 m-1 个节点到达这个反转部分的第一个结点,prev 自然是指向前驱了。
以下头尾说的是反转部分的头尾- 通过示例可以看出来,反转部分的头跑到了尾,尾被拉去当头,那么反转完毕以后这一部分需要和没有反转的连接啊,等我反转完了就到了尾巴,那去哪找反转部分原来的头啊。所以说不过有用没用先把头暂存起来,不用我再delete,想不到名了,既然反转以后这头成了尾,那就定义 last = cur 吧…然后再一个 dummy = prev;
- 接下来,反转吧!对了,反转多少个节点?不说了数一下就行了,反正反转到 prev 指向尾节点就行了;
- 反转完毕。需要连接了,首先过一遍: dummy 是反转部分的前一个结点;last 是反转前的头,反转后的尾;prev 是反转前的尾,反转后的头;cur 是反转部分后一个结点。好了,很清晰了:dummy.next = prev; last.next = cur; Okay了…然后运行出错 nullpointer ,嗯?拿着这 出错数据 面向测试用例编程,哦错这了:m 是1的时候,这 dummy 刚好是 null,那是 null 的时候一想,这前面全反转了,这整个链表的 head 刚好就是反转以后的prev,好了两种情况:dummy 是不是空。完结。
代码实现
public class Solution92 {
/**
* Definition for singly-linked list
*/
class ListNode {
int val;
ListNode next;
public ListNode(int val) {
this.val = val;
}
}
/**
* 输入: 1->2->3->4->5->NULL, m = 2, n = 4
* 输出: 1->4->3->2->5->NULL
* 1.首先定义 cur 和 prev 双指针
* 2.cur 指向反转链表的头结点; prev 指向反转链表的前驱结点. 给定开始位置为 m, 那就移动 m-1 个结点
* 3.需要先将这两个指针存起来在反转链表以后进行修改相应结点的指向, 所以 dummy = prev; last = cur;
* 4.反转相应区间的链表, 需要注意的是需要修改 n-m+1 个结点
* 5.在反转完链表后, 此时 prev 指向最后一个修改结点, cur 指向下一个
* 类似于 1<-2<-3<-4->5->NULL, 修改 1->4 ; 2->5
* 6.需要将 prev 放在 dummy 后面, 而 cur 放在 last 后面
*/
public ListNode reverseBetween(ListNode head, int m, int n) {
if (head == null) return null;
ListNode cur = head;
ListNode prev = null;
// 标记反转链表的结点个数
int step = n - m + 1;
// 使 prev 指向反转链表头结点的前驱结点
// cur指向反转链表的头结点
while (m-- > 1) {
prev = cur;
cur = cur.next;
}
// 将指针另存起来方便最后修改结点指向
ListNode dummy = prev;
ListNode last = cur;
// 反转链表
while (step-- > 0){
// 保存没有反转之前结点的 next
ListNode curNext = cur.next;
cur.next = prev;
prev = cur;
cur = curNext;
}
// m==1 时,dummy 为空
if (dummy != null) {
dummy.next = prev;
}else {
head = prev;
}
last.next = cur;
return head;
}
}
-----------------------------------------------------------------------------有始有终分割线----------------------------------------------------------------------------------