一、 开篇
上篇涉及到的链表的操作,每一题涉及到的链表操作的某一方面的较多,综合性一般,而专门用一篇博客来记录反转链表,正是因为它涉及到了链表的多种操作,综合性较强,更是面试重点,比较考验我们的逻辑分析能力和代码复现,话不多说,我们从几个角度来分析这道经典题型。
二、反转链表
2.1 允许使用虚拟节点
可以使用虚拟节点,那我们在逻辑上分析就会清晰一些。主要的思想就是,在虚拟节点和首节点之间不断增加原链表数据,最后遍历到原链表尾结点,也就成为了新链表的虚拟节点的下一个节点,也就是新链表的首节点。
从文字上来分析显得比较抽象,我们以图解来看,就好理解了:
由此我们就可以组织代码逻辑了,如下:
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null){
return null;
}
ListNode dumyNode = new ListNode(0);
ListNode cur = head;
while(cur!=null){
ListNode next = cur.next;
cur.next = dumyNode.next;
dumyNode.next = cur;
cur = next;
}
return dumyNode.next;
}
}
这里一个需要注意的点在于,通过新建的一个节点去保存我们在遍历过程中的临时节点,因为原来的cur的下一个节点指向需要变化!!
2.2 不使用虚拟节点
直接在原链表上进行反转,逻辑上应该比我们建立虚拟节点复杂一些,但是,我们是否也可以借鉴一下虚拟节点的处理方式呢?那肯定是可以的,我也是成功通过画图理清了过程的逻辑,我们直接上图理清逻辑:
到这里我们准备工作就做完了,可以看到这和通过虚拟节点实现的方式大同小异,但是最为重要的就是下一步的指针变化了!!
我们图中就是一次while循环的结果了,理清思路,我们代码也好写了:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null){
return null;
}
if(head.next == null) {
return head;
}
//尝试不使用虚拟节点
ListNode slow = head;
ListNode fast = head.next;
while(fast!=null){
if(slow == head){
slow.next=null;
}
ListNode next = fast.next;
fast.next = slow;
slow = fast;
fast = next;
}
return slow;
}
}
到此,分析结束,但是代码还是有一定的瑕疵,我们把只有一个节点的单独拎出去了,可不可以把它也囊括进来让代码更简化呢?要囊括一个节点的情况,那么我们开始定义的节点肯定不能是两个了,只定义一个指向头结点的指针,我们怎么构造出反转呢?难道定义一个指向空的节点!!!?
我们画图试试:
这不正好是一轮吗?代码实现如下:
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null){
return null;
}
//尝试不使用虚拟节点
ListNode cur= head;
ListNode prev= null;
while(cur!=null){
ListNode next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
}
}
更简明扼要了!可以说很巧妙
三、总结
反转链表对于链表相关题目来说,可以说真正搞懂几乎可以做链表的大量题目了。但还是要不断练习,锻炼我们的思维逻辑。