链表算法一:反转链表
题目:给定一个带头结点的单链表,按照其逆序输出。
输入:head->1->2->3->4->5->6->7
输出:head->7->6->5->4->3->2->1
注意:由于链表和数组结构不同,单链表中每个结点的地址都储存在其前结点的指针域中,因此,访问任何一个结点只能从链表的头指针开始进行遍历。
方法一:迭代
在遍历列表时,将当前节点的 next 指针改为指向前一个元素。由于节点没有引用其上一个节点,因此必须事先存储其前一个元素。在更改引用之前,还需要另一个指针来存储下一个节点。最后返回新的头引用!
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null) return null;
ListNode pre = null, next = null;
while(head.next != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
head.next = pre;
return head;
}
}
性能分析:这种方法智需要对链表进行一次遍历,所以时间复杂度为O(n)。但是需要额外的变量来保存当前结点的前驱结点和后继结点,所以空间复杂度为O(1)。
方法二:递归
递归法的主要思路是,先逆序除了第一个结点外的子链表(将1-2-3-4-5-6-7变为1-7-6-5-4-3-2),接着把结点1添加到逆序的子列表后面。同理逆序第二个元素依次类推。
class Solution {
public ListNode reverseList(ListNode head) {
if (head ==null || head.next == null) {
return head;
}
ListNode pre = reverseList(head.next);
head.next.next = head;
head.next = null;
return pre;
}
}
性能分析:该算法的也只需遍历链表一次,因此算法的时间复杂度也是O(n),他的优点是思路比较直观。但是由于递归法要不断调用自己,因此会导致性能有所下降。
方法三:利用栈
我们可以利用栈的先入后出性质,可以对链表进行反转
class Solution {
public ListNode reverseList(ListNode head) {
if (head ==null||head.next == null) {
return head;
}
Stack <ListNode> stack = new Stack<ListNode>();
ListNode cur = head;
while(cur!=null){
stack.push(new ListNode(cur.val));
cur = cur.next;
}
cur = stack.pop();
ListNode newHead = cur;
while ( !stack.isEmpty() ) {
cur.next = stack.pop();
cur = cur.next;
}
return newHead;
}
}
性能分析:该算法的也需额外的控件,整个过程需要遍历2次链表,即造成了时间的浪费,也造成了空间的浪费。优点:好理解。