算法通关村第二关——链表反转青铜挑战笔记
链表反转
三种方案:
- 虚拟头结点辅助反转(我觉得太麻烦了)
- 直接操作链表实现反转
- 递归实现反转(工作中不会使用,不过我这里详细的说明了)
1. 直接操作链表实现反转
首先这是链表
因为反转过来之后,1这边指向null,所以定义一个结点为prev = null,用来记录当前节点的前一个
ListNode prev = null,ListNode cur = head(头节点)
反转第一个结点,将cur.next = prev,那么问题就是怎么移动cur呐?所以还需要定义一个ListNode next = cur.next
保存下一个位置,只需要cur = next,这样就等于移动节点(其实就是cur = cur.next ,只是我们需要改变指针后再移动)
下面就是大概代码:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode cur = head;
while(cur != null){
ListNode next = cur.next;
cur.next = prev;
prev = cur
cur = next;
.....
}
}
}
这里注意,当cur = null时,5正好就是prev,也就是新链表的头节点,所以,返回的新链表,就是prev
public static ListNode reverseListSimple(ListNode head) {
ListNode prev = null;
ListNode cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = prev;
prev = curr;
cur = next;
}
return prev;
}
然后一步步下去,最后,返回return即可
2. 递归
参考学习视频链接:LeetCode:206.反转链表 | 双指针法 | 递归法
其实上面那种方法搞懂了,这一题使用递归就不难了
个人认为递归一定要画图,把图画好才懂得快,画图才会懂得递归三要素
递归三要素
三要素其实就是递归的模板,按照三要素来写递归就是递归的套路。
1、确定递归函数的参数和返回值:确定递归过程中需要处理的参数,明确每次递归的返回值进而确定递归函数的返回类型
2、确定递归终止条件,即函数return的出口:终止条件写的不对,操作系统的内存栈一定会溢出,毕竟递归也是有限制的
3、确定单层递归的逻辑:明确每次递归要进行什么操作
第一步:函数怎么往前递归
链表往前移动就是通过:head = head.next;
返回值是什么?ListNode。
由上面两个就可以得出:ListNode newHead = reverseListByRecurse(head.next);
第二步:确定递归终止条件
这里首先要明白,递归就类似与压栈的形式,出栈的时候再进行移动
我们从这个图可以看出,当public static ListNode reverseListByRecurse(ListNode head)
参数head.next为空的时候,就是终止条件:
注意终止条件记得极限情况,当头节点就是空的时候,那就直接不用递归了
那么代码就是这样:
public static ListNode reverseListByRecurse(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = reverseListByRecurse(head.next);
.....
return newHead;
}
这里retuen newHead,之后,就可以开始我们的逻辑
第三步:确定要执行的操作
从图可得出,head.next.next = head;就是操作
这时候由图可得,会发现,head得指针域,head.next还指向3,所以还需要加上,head.next = null
最后的代码如下:
public static ListNode reverseListByRecurse(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = reverseListByRecurse(head.next);
head.next.next = head;
head.next = null;
return newHead;
}