​welcome to my blog​

剑指offer面试题24(java版):反转链表

题目描述

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null

最初版的代码如下, while循环中很不好, 其实从初始化Next就注定了这个while写不好了

不要根据初始化写代码, 而是根据分析问题时的步骤写代码

反转链表的子过程: 1.保存Curr.next 2.改变Curr.next的指向 3.向后移动Pre和Curr

上面的过程1,2,3需要对每个节点执行一次

第二次做 反转链表是基础,务必牢牢掌握。 循环中,存,变,更

  • 循环中:curr!=null
  • 存:存储当前节点的下一个节点
  • 变:改变当前节点的指向
  • 更:更新left,curr
  • right单独更新,left,curr一起更新
/*
循环中:
先保存当前节点的下一个节点,
再改变当前节点的指向,
最后让left指向当前节点,让当前节点指向之前保存的下一个节点
*/
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head == null || head.next == null)
return head;
//三指针
ListNode left=null, curr=head, right = null;
while(curr != null){
//保存下一个节点
right = curr.next;
//change direction
curr.next = left;
//update lef, right
left = curr;
curr = right;
}
return left;
}
}

第二次做, 使用栈; 可能有助于理解递归?

import java.util.Stack;

public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null)
return head;
Stack<ListNode> s = new Stack<>();
ListNode curr=head;
while(curr!=null){
s.push(curr);
curr = curr.next;
}
curr = s.pop();
ListNode newHead=curr, left;
while(!s.isEmpty()){
//
left = s.pop();
//
curr.next = left;
left.next = null;
//
curr = left;
}
return newHead;
}
}

第二次做,递归版,感受递归过程; 注意:返回的是base case处碰到的节点

  • 递归函数返回新的头结点
  • head.next.next = head;改变了链表的指向
  • head.next = null; 让最后弹出的节点指向空
  • base case:主要用的是head.next == null判断当前是否是最后一个节点
  • 另一个head==null主要是Input check
  • 巧妙使用了栈:先入后出的特性
public class Solution {
public ListNode ReverseList(ListNode head) {
//base case
if(head == null || head.next == null)
return head;
//
ListNode newHead = ReverseList(head.next);
head.next.next = head; // head.next.next,区分next的赋值操作和直接使用
head.next = null; //这一句的作用是为了让最后弹出的节点指向null
return newHead;
}
}

第二次做, 递归版, 感受递归过程; 感受:left,curr一起更新, right单独更新; 感受:存,变,更

  • 递归函数返回的是反转后链表的头结点
/*
public class ListNode {
int val;
ListNode next = null;

ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
if(head==null || head.next==null)
return head;
//这里体现出:反转链表时,left,curr一起更新, right单独更新
return Core(null, head);
}
public ListNode Core(ListNode left, ListNode curr){
//base case
if(curr == null) return left;
//cun
ListNode right = curr.next;
//bian
curr.next = left;
//geng
left = curr;
curr = right;
return Core(left, curr);
}
}
public class Solution {
public static ListNode ReverseList(ListNode head) {
/*
反转列表
每次处理需要知道三个节点, Pre, Curr, Next
其中,Curr要从第一个节点遍历到最后一个节点
随之而来的就是,Pre,Next可能指向某个节点,也可能指向null
*/
// input check;
if (head == null || head.next == null)
return head;
//execute
ListNode Pre = null;
ListNode Curr = head;
ListNode Next = head.next; // 注意Next的初始化
while (Curr != null) {
Curr.next = Pre;
Pre = Curr;
Curr = Next;
if(Curr == null)
return Pre;
Next = Next.next;
}
return Curr;
}
}

笔记

  • 分析问题时找出的通用步骤, 适合于哪些节点?

改进后的代码(循环版)

public class Solution {
public static ListNode ReverseList(ListNode head) {
/*
反转列表
每次处理需要知道三个节点, Pre, Curr, Next
其中,Curr要从第一个节点遍历到最后一个节点
随之而来的就是,Pre,Next可能指向某个节点,也可能指向null
*/
// input check;
if (head == null || head.next == null)
return head;
//execute
ListNode Pre = null;
ListNode Curr = head;
ListNode Next = null; // 注意Next的初始化
while (Curr != null) {
Next = Curr.next; // 保存next
Curr.next = Pre; // 反转
Pre = Curr;
Curr = Next;
}
return Pre;
}
}

递归版代码

public class Solution {
public ListNode ReverseList(ListNode head) {
/*
反转列表
每次处理需要知道三个节点, Pre, Curr, Next
其中,Curr要从第一个节点遍历到最后一个节点
随之而来的就是,Pre,Next可能指向某个节点,也可能指向null
*/
// input check;
if (head == null || head.next == null)
return head;
//execute
return ReverseListCore(null, head);
}

public ListNode ReverseListCore(ListNode Pre, ListNode Curr){ // 没有必要在参数中加入Next, 这样当Curr为null时, 没有Next, 还得额外判断, 浪费时间
// 递归终止条件
if(Curr == null)
return Pre;
// execute
ListNode Next = Curr.next; // 保存下一个节点
Curr.next = Pre; // 改变指向
return ReverseListCore(Curr, Next);
}
}