反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
解法一:迭代
重复某一过程,每一次处理结果作为下一次处理的初始值,这些初始值类似于状态、每次处理都会改变状态、直至到达最终状态。
从前往后遍历链表,将当前节点的next指向上一个节点,因此需要一个变量存储上一个节点prev,当前节点处理完需要寻找下一个节点,因此需要一个变量保存当前节点curr,处理完后要将当前节点赋值给prev,并将next指针赋值给curr,因此需要一个变量提前保存下一个节点的指针next。
1、将下一个节点指针保存到next变量 next = curr.next
2、将下一个节点的指针指向prev,curr.next = prev
3、准备处理下一个节点,将curr赋值给prev
4、将下一个节点赋值为curr,处理一个节点
//迭代(双指针)
public ListNode reverseList1(ListNode head){
ListNode prev = null;//前指针节点
ListNode curr = head;//当前指针节点
ListNode temp = null;//临时节点,暂存当前指针的后继节点
while (curr != null){
temp = curr.next;//暂存当前指针的后继节点
curr.next = prev;//改变curr->next的指向
prev = curr;//前指针后移
curr = temp;//当前指针后移
}
return prev;
}
时间复杂度 O(N): 遍历链表使用线性大小时间。
空间复杂度 O(1): 变量 pre 和 cur 使用常数大小额外空间。
解法二:递归1
以相似的方法重复,类似于树结构,先从根节点找到叶子节点,从叶子节点开始遍历大的问题(整个链表反转)拆成性质相同的小问题(两个元素反转) curr.next.next = curr 将所有的小问题解决,大问题即解决。
在“递”的过程中,在栈中依次保留了所有的前序节点,在“归”的过程中后续节点指向前序节点。
只需每个元素都执行curr.next.next = curr,curr.next = null两个步骤即可
为了保证链不断,必须从最后一个元素开始
//curr即传入的head
public static ListNode reverseList2(ListNode curr) {
if (curr== null || curr.next == null) {
return curr;
}
ListNode newHead = reverseList2(curr.next);
curr.next.next = curr;
curr.next = null;
return newHead;
}
解法三:递归2
reverse(head,null) 递归函数
终止条件: 当 curr 为空,则返回尾节点 prev (即反转链表的头节点);
递归后继节点,记录返回值(即反转链表的头节点)为 res ;
修改当前节点 cur 引用指向前驱节点 pre ;
返回反转链表的头节点 res ;
public ListNode reverseList3(ListNode head) {
return recur(head, null); // 调用递归并返回
}
private ListNode recur(ListNode cur, ListNode pre) {
if (cur == null) return pre; // 终止条件
ListNode res = recur(cur.next, cur); // 递归后继节点
cur.next = pre; // 修改节点引用指向
return res; // 返回反转链表的头节点
}
复杂度分析: 时间复杂度 O(N): 遍历链表使用线性大小时间。
空间复杂度 O(N): 遍历链表的递归深度达到 N,系统使用O(N)大小额外空间。