206、难度简单:
要求:链表可以选用迭代或递归方式完成反转
从概念上讲,递归就是指程序调用自身的编程思想,即一个函数调用本身;迭代是利用已知的变量值,根据递推公式不断演进得到变量新值得编程思想。从直观上讲,递归是将大问题化为相同结构的小问题,从待求解的问题出发,一直分解到已经已知答案的最小问题为止,然后再逐级返回,从而得到大问题的解(一个非常形象的例子就是分类回归树 classification and regression tree,从root出发,先将root分解为另一个(root,sub-tree),就这样一直分解,直到遇到leafs后逐层返回);而迭代则是从已知值出发,通过递推式,不断更新变量新值,一直到能够解决要求的问题为止。
从“编程之美”的角度看,可以借用一句非常经典的话:“迭代是人,递归是神!”来从宏观上对二者进行把握。
方法一:迭代法:时间复杂度:O(n),其中 n 是链表的长度。需要遍历链表一次。空间复杂度:O(1)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
}
原理:假设链表为 1→2→3→∅,我们想要把它改成 ∅←1←2←3。
在遍历链表时,将当前节点的 \textit{next}next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。
pre是前一个指针,由于链表的最后指向null,所以pre初始值为null
curr是指当前指针
next是当前指针的下一个指针
循环过程:当前指针指向前一个指针,一个循环结束;所以当前指针成为了下一个指针的前一个指针;当前指针成为了下一个指针;
为防止当前指针指向前一个指针后丢失其原先的下一个指针及往后,所以新创建指针next用于保存。
方法二:递归:时间复杂度:O(n) 其中 n 是链表的长度。需要对链表的每个节点进行反转操作。
空间复杂度:O(n) 其中 n 是链表的长度。空间复杂度主要取决于递归调用的栈空间,最多为 n 层。
//以链表1->2->3->4->5举例
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
/*
直到当前节点的下一个节点为空时返回当前节点
由于5没有下一个节点了,所以此处返回节点5
*/
return head;
}
//递归传入下一个节点,目的是为了到达最后一个节点
//直到节点.next = null为止,可以看成一直在执行上面的if判断和下面这一行的传递head.next语句。
ListNode newHead = reverseList(head.next);
/*
第一轮,head为5,head.next为空,进入if判断内,上面的return返回5,newHead成为5。
此时head为4,开始执行下面的head.next.next = head;让5指向4;
head.next = null;让4指向空;返回 5;
第二轮,此时链表顺序为1->2->3->4<-5
此时head为3,head.next为5,执行head.next.next=head也就是4.next=3,同理往下得
此时链表为1->2->3<-4<-5,返回 5
直到1<-2<-3<-4<-5时,返回5。没有下一轮了,结束。
最终头节点5->4->3-2->1
*/
head.next.next = head;
head.next = null;
return newHead;
}
}
原理:关键在于反向工作。假设链表的其余部分已经被反转,现在应该如何反转它前面的部分?
假设链表为:n1 →…→ nk−1 → nk → nk+1 → … → nm →∅
若从节点 nk+1到 nm 已经被反转,而我们正处于 nk
n1→…→ n k−1 → nk → nk+1 ← … ← nm
我们希望 nk+1 的下一个节点指向 nk
所以,nk.next.next = nk 需要注意的是 n1 的下一个节点必须指向 ∅。如果忽略了这一点,链表中可能会产生环。
21、难度简单:
方法一:递归:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
**/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
} else if (l2 == null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
原理:也就是当list1的头值小于list2时,就把list[0]和list2进行拼接,否则,就把list[2]和ist1进行拼接。对该过程进行递归
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oQILa0AJ-1632619750314)(21. 合并两个有序链表.assets/image-20210926085539311.png)]
如果 l1 或者 l2 一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。如果两个链表有一个为空,递归结束。
代码过程:
两链表谁为空就返回另一方
否则,执行上面的原理。假设list1 = 1 4 5 9,list2 = 2 3 6 10
比较list1 [0]和list2 [0]:1小于2,应将list1 [0]和list2 [0]连接,让list1 [0]指向mergeTwoLists(l1.next, l2);
比较list1 [1]和list2 [0]:4大于2,应将list2 [0]和list1 [1]连接,让list2 [0]指向mergeTwoLists(l1, l2.next);
比较list1 [1]和list2 [1]:略
直到两个参数中的一个.next为null时,返回另一方。
也就是比较完list1 [3]和list2 [3]后传递参数list1中3.next时发现为空,返回list2(值10),此时将list1 [3]指向了list2 [3],返回list1 [3]
也就是list2 [2]又将指向list1 [3]
同理…
方法二:迭代
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
}
原理:创建一个新节点,作为结果链表的头结点。当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。
代码过程:
prehead.next作为最后返回的结果链表的头结点存在
prev作为构建prehead链表的辅助结点
while循环:prev指向 l1 和 l2中更小的一方,同时将被指向的节点向后移一位,以便下次比较。
当l1和l2有一方为空后就退出循环,若存在不为空的一方,就让prev指向那一方。