题目描述:给定一个链表,将链表进行反转。
示例1:
输入:1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
题目理解:首先先接收一个链表,然后根据链表的性能进行反转。
解法1:借助栈的后进先出的功能,先扫描一遍链表,在栈中保存每个节点的值,然后再从头到尾遍历,将栈中的元素按次序弹出赋给链表的节点,时空复杂度都是O(n).
package TEST;
import java.util.Stack;
/**
* @Author:
* @Description:反转链表;
* @Date: Created in 2018/9/3 23:14
* @Pram:
*/
public class Node {
private Node next;
private String val;
public Node(String val){
this.val = val;//节点的值;
}
public Node() {
}
public static void ReverseList(Node node){
Stack<Node> stack = new Stack<Node>();//定义一个栈;
while(node!=null){//链表不为空就开始遍历;
stack.push(node);//入栈;
node = node.next;
}
while(!stack.empty()){
Node temp = stack.pop();
System.out.println(temp.val);
}
}
public static void main(String[] args) {
Node node1 = new Node("A");
Node node2 = new Node("B");
Node node3 = new Node("C");
node1.next = node2;
node2.next = node3;
System.out.println("链表反转后是:");
ReverseList(node1);
node1.next = null;
}
}
解法2:可以做到in-place的反转。链表反转后,实际上只是中间节点的指针反转,并且反转后原来的链表的头节点的下一个节点应该为null,而反转后链表的头节点为原来链表的结尾处的尾节点。从头节点开始处理每两个节点之间的指针将其反转过来,然后处理接校来的指针,直到尾节点结束。并设置新的链表的头节点即可。
public class ListNode{
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
public class Solution{
public ListNode reserseList(ListNode head){
if(head == null || head.next == null){
return head;
}
ListNode next = head.next;
head.next = null;
while(next.next != null){
ListNode node = next.next;
next.next = head;
head = next;
next = node;
}
next.next = head;
return next;
}
}
解法3--迭代:首先,肯定知道最后返回的结果是输入链表的链表尾节点。但先找到尾节点是很难继续实现的,因为链表没有办法高效获取前驱。往往这类问题很多时候都要想到建立一个新的节点,之后在遍历输入的时候重新组织节点顺序,将节点挂在新节点上。所以高效的做法是在遍历链表的过程中,一个一个的把输入链表的节点放到一个新的链表头部。所以思路就是建立一个新的链表头,每次遍历输入链表的节点都把他放到新链表的头部,这样遍历完成后就获得了反转的链表。详细代码注释见下。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode first = head;
ListNode reverseHead = null; //建立一个新的节点用来存放结果
while (first != null) { //遍历输入链表,开始处理每一个节点
ListNode second = first.next; //先处理第一个节点first,所以需要一个指针来存储first的后继
first.next = reverseHead; //将first放到新链表头节点的头部
reverseHead = first; //移动新链表的头指针,让它始终指向新链表头部
first = second; //继续处理原链表的节点,即之前指针存放的后继,循环往复
}
return reverseHead;
}
}
解法4--递归:
递归:
每次想着用递归解法我习惯于用数学归纳法的思维去思考。先想输入规模最小的情况,再想比较general的情况。就本题来说,如果输入的是null或者单节点链表,必然是返回其本身。如果至少有两个节点,那么才开始递归。想一下,递归后的结果一定是一个规模更小问题的结果。即如果输入有k个节点,那么递归调用程序,输入原链表第二个节点所返回的结果,是一个反转后的,拥有k-1个节点的链表的首节点 —— 规模更小的问题的结果。那么如果把这个递归调用后返回的头节点所指向链表的尾节点的next域,指向被调用的节点的前驱,就相当反转了k个节点的链表。即利用k-1的结果去完成了k的问题。所以想到这里,在递归函数里要做的就是三件事:第一,记录即将被递归调用节点的前驱(或者换句话说,建立个新的节点指向输入的下一个节点,之后递归调用那个新节点);第二,递归调用输入的下一个节点;第三,将返回结果的末尾指向记录好的前驱节点,完成反转。
这里需要注意的只有第三步,如何找到返回的结果链表的末尾。还是要回归到递归的本质,即返回的结果是一个已经反转完成的链表的首节点。反转完成的意思就是我们输入一个以节点S为头结点,节点E为尾结点的链表,那么调用后返回的节点是E,而S经过调用后变成了尾节点。即,递归调用时的输入本身,即是调用完成后我们需要的尾节点!所以我们并不需要每一次都去寻找递归调用后结果的尾节点,只需要直接利用递归调用的输入即可,因为这个输入就是调用完成的尾节点。详细代码注释见下。
/**
* 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 || head.next == null) return head; //处理最小输入的情况,即空链表和单节点链表
ListNode second = head.next; //将即将被调用的下一个节点分离,即将下一个调用的输入存在second里
ListNode reverseHead = reverseList(second); //将调用后的结果存储,这个结果就是最终结果。之后利用递归,调用刚才存好的输入
second.next = head; //上面一步的调用已经完成以second为首的链表的反转,所以现在second变成了反转完成后的尾节点
//把这个尾节点的next指向一开始输入的前驱,即head,完成整个链表反转
head.next = null; //最开始的头节点要变成尾节点,即在后面补null使链表终结
return reverseHead;
}
}