(刷题笔记)链表相关——双指针解法(虽然简单,但思想重要)
文章目录
1、合并两个有序链表
21. 合并两个有序链表 - 力扣(LeetCode) (leetcode-cn.com)
while 循环每次比较 p1
和 p2
的大小,把较小的节点接到结果链表上,使用 dummy
节点,可以避免处理空指针的情况,降低代码的复杂性
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
ListNode p = list1;
ListNode q = list2;
while(p!=null && q !=null){
if(p.val < q.val){
tail.next = p;
p = p.next;
}
else{
tail.next = q;
q = q.next;
}
tail = tail.next;
}
if(p!=null)
tail.next = p;
if(q!=null)
tail.next = q;
return dummy.next;
}
}
2、合并 k 个有序链表
23. 合并K个升序链表 - 力扣(LeetCode) (leetcode-cn.com)
利用了优先队列(小顶堆),每次将每个链表的头放进去,思想很巧妙
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> pq = new PriorityQueue<>((a,b)->a.val-b.val);
for(ListNode list : lists)
// 避免空指针异常
if(list!=null)
pq.add(list);
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
while(!pq.isEmpty()){
ListNode q = pq.poll();
p.next = q;
p = p.next;
if(q.next!=null)
pq.add(q.next);
}
return dummy.next;
}
}
3、单链表的倒数第 k 个节点
剑指 Offer 22. 链表中倒数第k个节点 - 力扣(LeetCode) (leetcode-cn.com)
真的很简单了,细节要注意。
快慢指针,快指针先走k步,然后快慢指针一起走,快指针走到尾端的时候,slow就是倒数第k个节点
ps:面试的时候还是要说一下,这个输入的k是不是合法的?即k大于链表的长度
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode p = head;
ListNode q = head;
while (k>0){
q = q.next;
k--;
}
while (q!=null){
p = p.next;
q = q.next;
}
return p;
}
}
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode) (leetcode-cn.com)
和上题一样,既然要删除倒数第N个节点,那就找到倒数第N+1个节点就好了
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
dummy.next = head;
ListNode fast = dummy;
ListNode slow = dummy;
for(int i = 0; i < n + 1 ; i++)
fast = fast.next;
while(fast!=null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
4、单链表的中点
Loading Question… - 力扣(LeetCode) (leetcode-cn.com)
快指针每次走两步,慢指针每次走一步,快指针停了,慢指针就是中点了
注意while循环的条件
class Solution {
public ListNode middleNode(ListNode head) {
if(head.next == null)
return head;
ListNode fast = head;
ListNode slow = head;
// 要有fast!=null,否则空指针错误
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
}
5、判断链表是否包含环
141. 环形链表 - 力扣(LeetCode) (leetcode-cn.com)
和上题一样,快指针每次走两步,慢指针每次走一步,如果有环的话,最后快指针就会追上满指针
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null)
return false;
ListNode fast = head;
ListNode slow = head;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast)
return true;
}
return false;
}
}
142. 环形链表 II - 力扣(LeetCode) (leetcode-cn.com)
进阶,不仅要判断是否有环,还要找到环的起点。
假设快慢指针相遇时,慢指针 slow
走了 k
步,那么快指针 fast
一定走了 2k
步,只要把快慢指针中的任一个重新指向 head
,然后两个指针同速前进,相遇之处就是环的起点了
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head == null)
return head;
ListNode fast = head;
ListNode slow = head;
while(fast!=null && fast.next !=null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast)
break;
}
// 一定要有这句,当输入为1个节点的链表,且没有环的时候,没有这句就会有问题
if(fast == null || fast.next == null)
return null;
if(slow == fast){
slow = head;
while(slow!=fast){
slow = slow.next;
fast = fast.next;
}
return slow;
}
return null;
}
}
6、两个链表是否相交
160. 相交链表 - 力扣(LeetCode) (leetcode-cn.com)
这个题直接的想法可能是用 HashSet
记录一个链表的所有节点,然后和另一条链表对比,但这就需要额外的空间
可以让 p
遍历完链表 A
之后开始遍历链表 B
,让 q
遍历完链表 B
之后开始遍历链表 A
,这样相当于「逻辑上」两条链表接在了一起。如果这样进行拼接,就可以让 p
和 q
同时进入公共部分,也就是同时到达相交节点。
注意while循环体内的逻辑。如果两个链表没有相交的话,最后p和q都指向null,正确返回
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p = headA;
ListNode q = headB;
while(p!=q){
// 注意逻辑
if(p == null)
p = headB;
else
p = p.next;
if(q == null)
q = headA;
else
q = q.next;
}
return p;
}
}