技巧:使用虚拟节点prev(可以避免处理空指针的情况,降低代码的复杂性)
21. 合并两个有序链表 - 力扣(LeetCode) (leetcode-cn.com)
思路:将小的节点和prev相连
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode prev=new ListNode(-1);
ListNode cur=prev;
while(list1!=null&&list2!=null){
if(list1.val<list2.val){
cur.next=list1;
list1=list1.next;
}else{
cur.next=list2;
list2=list2.next;
}
cur=cur.next;
}
if(list1==null){
cur.next=list2;
}
if(list2==null){
cur.next=list1;
}
return prev.next;
}
23. 合并K个升序链表 - 力扣(LeetCode) (leetcode-cn.com)
思路:利用堆将小的节点筛选出来和prev相连
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length==0){
return null;
}
ListNode prev=new ListNode(-1);
ListNode cur=prev;
PriorityQueue<ListNode>queue=new PriorityQueue<>((o1,o2)->(o1.val-o2.val));
for(ListNode head:lists){
if(head!=null){
queue.add(head);
}
}
while(!queue.isEmpty()){
ListNode node=queue.poll();
cur.next=node;
cur=cur.next;
if(node.next!=null){
queue.add(node.next);
}
}
return prev.next;
}
优先队列 queue中的元素个数最多是 k,所以一次 poll 或者 add 方法的时间复杂度是 O(logk);
所有的链表节点都会被加入和弹出 queue,所以算法整体的时间复杂度是 O(Nlogk),其中 k 是链表的条数,N 是这些链表的节点总数。
技巧:双指针
剑指 Offer 22. 链表中倒数第k个节点 - 力扣(LeetCode) (leetcode-cn.com)
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode p1=head;
ListNode p2=head;
while(k!=0){
p1=p1.next;
k--;
}
while(p1!=null){
p1=p1.next;
p2=p2.next;
}
return p2;
}
时间复杂度是 O(N)
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode) (leetcode-cn.com)
使用了虚拟头结点的技巧,也是为了防止出现空指针的情况,比如说链表总共有 5 个节点,题目就让你删除倒数第 5 个节点,也就是第一个节点,那按照算法逻辑,应该首先找到倒数第 6 个节点。但第一个节点前面已经没有节点了,这就会出错。
但有了我们虚拟节点 dummy
的存在,就避免了这个问题,能够对这种情况进行正确的删除。
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode prev = new ListNode(-1);
prev.next = head;
ListNode node = findFromEnd(prev, n + 1);
node.next = node.next.next;
return prev.next;
}
public ListNode findFromEnd(ListNode head, int k) {
ListNode p1 = head;
ListNode p2 = head;
while(k!=0){
p1 = p1.next;
k--;
}
while (p1 != null) {
p2 = p2.next;
p1 = p1.next;
}
return p2;
}
876. 链表的中间结点 - 力扣(LeetCode) (leetcode-cn.com)
public ListNode middleNode(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
141. 环形链表 - 力扣(LeetCode) (leetcode-cn.com)
思路:快慢指针相遇证明有环
public boolean hasCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
if(slow==fast){
return true;
}
}
return false;
}
142. 环形链表 II - 力扣(LeetCode) (leetcode-cn.com)
思路:当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(slow==fast){
break;
}
}
if(fast==null||fast.next==null){
return null;
}
slow=head;
while(slow!=fast){
slow=slow.next;
fast=fast.next;
}
return slow;
}
160. 相交链表 - 力扣(LeetCode) (leetcode-cn.com)
思路:让 p1
遍历完链表 A
之后开始遍历链表 B
,让 p2
遍历完链表 B
之后开始遍历链表 A
,这样相当于「逻辑上」两条链表接在了一起。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1=headA;
ListNode p2=headB;
while(p1!=p2){
if(p1==null){
p1=headB;
}else{
p1=p1.next;
}
if(p2==null){
p2=headA;
}else{
p2=p2.next;
}
}
return p1;
}
技巧:递归
206. 反转链表
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
反转链表前 N 个节点
ListNode Next = null; // 后驱节点
// 反转以 head 为起点的 n 个节点,返回新的头结点
ListNode reverseN(ListNode head, int n) {
if (n == 1) {
// 记录第 n + 1 个节点
Next = head.next;
return head;
}
// 以 head.next 为起点,需要反转前 n - 1 个节点
ListNode last = reverseN(head.next, n - 1);
head.next.next = head;
// 让反转之后的 head 节点和后面的节点连起来
head.next = Next;
return last;
}
92. 反转链表 II
ListNode reverseBetween(ListNode head, int m, int n) {
if (m == 1) {
return reverseN(head, n);
}
head.next = reverseBetween(head.next, m - 1, n - 1);
return head;
}
ListNode Next = null; // 后驱节点
ListNode reverseN(ListNode head, int n) {
if (n == 1) {
Next = head.next;
return head;
}
ListNode last = reverseN(head.next, n - 1);
head.next.next = head;
head.next = Next;
return last;
}