链表反转之所以重要,是要为其涉及到结点的增加、删除等多种操作,可以有效考察思维能力和代码驾驭能力,此外它也是很多题目的的基础,比如指定区间反转、链表K个一组反转。
力扣206给出一个单链表,将链表进行反转并返回反转后的链表。
有两种较好的解法,带头结点和不带头结点(迭代法)。
1.建立虚拟头结点辅助反转
虚拟头结点可以帮助我们较好地处理头结点,简化操作。首先建立虚拟头结点,接下来每次从给定链表拆下来一个结点接到虚拟头结点后面,然后将其他线调整好即可。
var reverseList = function(head) {
let dummyHead = new ListNode(-1);
let nextNode = new ListNode(0);
let currentNode = head;
while (currentNode) {
nextNode = currentNode.next; // 待处理链表
currentNode.next = dummyHead.next; // 将当前结点接到虚拟头结点后面,得到反转后的链表
dummyHead.next = currentNode; // 复原虚拟头结点
currentNode = nextNode; // 继续遍历待处理链表
}
return dummyHead.next;
}
2.迭代法,不用虚拟头结点
如图,prev
表示已经反转好的新链表的表头,cur
为待反转的给定链表表头,next
为下一个要进行反转的结点。需要注意prev
和cur
为两个表的表头,在反转过程中cur
经过一次中间状态之后,又重新变成给定链表的表头。
在遍历链表时,将当前节点的 next 指针改为指向前一个节点。
由于当前节点没有引用其前一个节点,因此必须事先存储其前一个节点。
在更改引用之前,还需要存储后一个节点。
最后返回新的cur结点。
var reverseList = function(head) {
if (!head) return null;
let previousNode = null;
let currentNode = head;
while (currentNode) {
const nextNode = currentNode.next;
currentNode.next = previousNode;
previousNode = currentNode;
currentNode = nextNode;
}
return previousNode;
}
3.拓展:递归法(不如前两种方法好)
// 递归方法
var reverseList = function(head) {
if (!head || !head.next) return head;
let newHead = new ListNode(0);
newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}