单链表的反转(面试常出):
单链表的反转,可以通过很多种方法实现。包括迭代法,递归法,
-
迭代法:
-
定义三个指针:prev、current和next,它们分别表示前一个节点、当前节点和下一个节点。
-
初始化时,prev为null,current为头节点。
-
在循环中,不断将current的next指针指向prev,然后依次向后移动prev、current和next指针。
-
当next为空时,说明已经到达链表末尾,此时的prev指向就是反转后的头节点。
其实就像是先将第一个节点指向null,就像最后一个节点它也是next = null的;然后一直这样子重复,转一次就后退,让下一个节点去转方向指向前面的。
public ListNode reverse(Node head) { Node prev = null; Node current = head; while (current != null) { Node nextTemp = current.next; // 暂存当前节点的下一个节点 current.next = prev; // 将当前节点指向前一个节点 prev = current; // 更新prev为当前节点 current = nextTemp; // 更新current为原先的下一个节点 } return prev; // 返回反转后的头节点 }
// 补充一个力扣第92题,加深理解(让你把单链表部分元素反转,其他部分不变)
-
这里依旧可以使用迭代的方法,就是要先通过遍历去找到反转开始的位置和结束的位置;用辅助节点记录反转区间的前置节点和反转区间的尾节点。然后反转区间的链表反转即可,反转后接上前面的部分。
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode current = head;
ListNode prev = null;
for (int i = 1; i < left; i++) {
prev = current;
current = current.next;
}
// 反转区间的前置节点
ListNode tailPrev = prev;
// 反转区间的尾节点
ListNode tail = current;
// 同样的迭代原理,只是范围要自定义。
for (int j = left; j <= right; j++) {
ListNode newTemp = current.next;
current.next = prev;
prev = current;
current = newTemp;
}
// 反转区间的头节点
ListNode headReverse = prev;
tail.next = current;
if (tailPrev != null) {
tailPrev.next = headReverse;
return head;
} else {
return headReverse;
}
}
递归法:(也是力扣第206题)
递归的关键是看整体,找出整体的大块东西。可以先写一个伪代码过一遍思路。你就写基础情况,然后要的操作,再看子问题(递归调用)放的位置即可。
**实操技巧:**第一步先找到可以看作整体的,就是原理一样的。去设计递归调用(超级操作),第二步去设计微操作,去看一份整体的怎么实现即可。后面就去找到题目中的基础情况,它相当于最里面的超级操作了,也基本就是剩下一个的情况那种。
相关概念:
1.首先先明确函数的意义,还要原问题和子问题。在这里原问题是给整个链表反转,子问题是给每一个字节进行反转。
2.基础情况,也就是要找到结束的条件。就是当数据规模很小的时候,能结束递归,返回答案。
3.递归调用(超级操作),怎么搞定中间的递归操作。但是!唉!人脑进去递归出不来的。所以得完把递归的过程看成一个整体,不要去想里面怎么运行的。
4.微操作。也就是操作的方法。
比如汉诺塔问题,你有2个叠在一起的时候,就是先把小的放中间,大的放右边,再把小的放大的上面。那这个时候,假如他有一堆,你就是把小的一堆给放到中间,让最大的去到最右边垫底。然后小的一堆整体放到大的上面。而那一堆小的移动其实就是整个问题的子问题,它其实就是可以用递归重复一个原理完成。
此题思路如下:
// 定义:输入一个单链表头结点,将该链表反转,返回新的头结点
ListNode reverse(ListNode head) {
// 基础情况,也就是结束的代码。
// 链表为空或者只有一个节点时,直接返回头节点即可。
if (head == null || head.next == null) {
return head;
}
// 递归调用(超级操作)
ListNode last = reverse(head.next);
// 而其实当你写一个伪代码时候,你也可以发现。下面的这个其实就是反转需要的的操作,可以写一个伪代码,微操作。具体操作方法: operate(head.next);
head.next.next = head;
head.next = null;
return last;
}