链表刷题总结
链表刷题总结
做了一段时间的链表题,多多少少也看了一些优秀题解,链表解题技巧无非就以下几种:
- 朴素解法,
head = head.next
遍历链表来解决问题; - 双指针,甚至是三指针,在很多的链表题中发挥很大的作用;
- 快慢指针,快指针和慢指针以不同的速度同时遍历链表;
- 递归,大多数链表题都能用递归来解决;
- 哑节点
dummy
,或者是哨兵节点,可以简化链表的极端操作;
朴素解法就不总结了,下面来说说我这几天对其他节点的感受和做过的题。
双指针
对于链表的题目,双指针的应用太多了。不论是删除链表的倒数第N个节点,还是回文链表,都会用到双指针。因为链表的随机访问效率差,当我们想要处于某个位置的节点的时,都需要去从头遍历,所以使用额外的指针来记录节点位置是非常有必要的。
-
删除链表的倒数第N个节点,对于这个问题,使用双指针
before
和after
,距离N
个节点,即before
先走N
步,然后after
和before
一起走。当before
到达链尾时,after
指向倒数第N
个节点。while (n-- > 0){ before = before.next; } while (before.next != null){ // 结束循环后slow指向需要删除节点的前一个节点 before = before.next; after = after.next; }
-
回文链表:双指针分别指向头尾,像中间靠近。
int front = 0; // 双指针分别指向头尾,向中间靠经 int back = vals.size() - 1; while (front < back){ if (!vals.get(front).equals(vals.get(back))){ // 使用equals return false; } front++; back--; }
-
旋转链表:本质上是找到倒数第
K
个节点,但是K
有可能大于链表的长度,所以可以先考虑将链表闭合,再找到相应位置断开这个环。// 记录链表节点数 int n; for (n = 1; old_tail.next != null; n++){ old_tail = old_tail.next; } // 形成闭环 old_tail.next = head; ListNode new_tail = head; for (int i = 0; i < n - k%n - 1; i++){ new_tail = new_tail.next; } ListNode new_head = new_tail.next; // 断开 new_tail.next = null; return new_head;
-
两两交换链表中的节点:使用三个指针,需要交换的节点
firstNode
和secondNode
,还有firstNode
的前一个节点prevNode
。while ((head != null) && (head.next != null)) { ListNode firstNode = head; ListNode secondNode = head.next; // 进行交换 prevNode.next = secondNode; firstNode.next = secondNode.next; secondNode.next = firstNode; // 对prevNode和head重新赋值,准备下一次的交换 prevNode = firstNode; head = firstNode.next; // jump }
链表中用到指针的地方非常多,同时在草稿纸上进行画图对解题也非常有帮助。
快慢指针
我觉得快慢指针其实也就是双指针的一种,只不过快慢指针是同时以不同的速度对链表进行遍历,来解决相关的问题。
- 链表的中间节点:
- 解决这个问题,我们当然可以对链表进行两次遍历。第一次遍历记录链表节点的总数,然后再遍历到链表的中点位置即可。
- 如果使用快慢指针呢?我们将快指针
fast
的速度设为2,即每次走两步,将慢指针slow
的速度设为1,每次走一步。这样,当fast
走到末尾时,slow
正好走到中间,完美解决题目。 - 思考:这个和上面找到倒数第N个节点的不同在哪?
- 使用双指针,两个指针不同时出发,这个时候我们对于先出发的指针需要先走多少步是已知的。
- 而对于这个题来说,链表的总结点我们不知道,所以我们并不知道要先走多少步,所以快慢指针同时出发,因为
fast
的速度是slow
的两倍,所以当fast
走到末尾时,slow
正好在中间。
- 环形链表
- 使用快慢指针解决环形链表问题,如果是环形链表,则在某一时刻快指针会追上慢指针,如果不是,快指针会先一步到达链表尾部,退出循环。
- 当我们需要找到环形链表的环头时,将快指针的速度设为2,慢指针速度设为1,当快慢指针相遇时,它们不一定在环的起始节点,所以我们在找到了相遇节点后,该如何找到环开始的位置呢?
- 数学思想:快指针的速度是慢指针的2倍,所以快指针走的路程是慢指针走的路程的两倍;
- 在分段画图分析后,我们可以得到这样的结论:从
head
出发与meet
相遇,两指针的速度一样,相遇时即为环的起点。
相关题目
-
(力扣)141.环形链表
-
(力扣)142.环形链表Ⅱ
-
(力扣)876.链表的中间节点
递归
使用递归函数,避免复杂的更改指针变量指向操作,使解题更加简单。
相关题目
-
(力扣)21.合并两个有序链表
-
将两链表的头的值进行比较,较小的节点的
next
为下一层递归的结果,本级递归返回两链表头较小的节点if (l1.val < l2.val) { l1.next = mergeTwoLists(l1.next, l2); return l1; }else { l2.next = mergeTwoLists(l1, l2.next); return l2; }
-
-
(力扣)23.合并K个升序链表
- 归并排序,我们将链表数组分成两个,分别对小链表数组进行合并。
- 通过递归将链表一直分割,直到链表只有1个时,此时链表是有序的,然后将两个有序的链表进行合并;
- 先对子列表进行递归,返回对列表内排序好的头节点;
- 然后再对当前的两个链表进行合并,合并调用上面21题的函数。
-
(力扣)24.两两交换链表中的节点
-
终止条件
- 当递归到链表为空或者只有1个元素时,无法进行交换,递归终止;
-
返回值
- 已经完成交换的子列表的头结点
-
单次递归过程
-
将待交换的两个节点设置为
firstNode
和secondNode
,firstNode
交换后在后面,所以firstNode.next
指向下一层递归返回的子链表,而secondNode
和firstNode
位置交换,所以secondNode.next
指向firstNode
。ListNode firstNode = head; ListNode secondNode = head.next; // 交换 firstNode.next = swapPairs(secondNode.next); secondNode.next = firstNode; // 返回 return secondNode;
-
-
-
(力扣)203.移除链表元素
-
递归边界,
head == null
-
先调用递归函数删除后面的目标元素,即
head.next = removeElements(head.next, val);
-
再判断当前节点是否等于目标元素
- 相等,返回
head.next
; - 不相等,返回
head
;
public ListNode removeElements(ListNode head, int val) { if (head == null){ return null; } head.next = removeElements(head.next, val); return head.val == val ? head.next : head; }
- 相等,返回
-
-
(力扣)206.反转链表
-
递归边界
- 当递归到链表为空或者只有1个元素时,不需要反转,递归终止;
-
返回值
- 返回已经反转好的子链表头节点
-
本级任务
- 将当前节点的下一节点的
next
指向当前节点
public ListNode reverseList(ListNode head) { if (head == null || head.next == null) return head; ListNode p = reverseList(head.next); head.next.next = head; head.next = null; return p; }
- 将当前节点的下一节点的
-
-
(力扣)234.回文链表
-
cur
先到尾节点,由于递归的特性再从后往前进行比较; -
front
是递归函数外的指针; -
如果
cur.val != front.val
,返回false
; -
否则,
front
向前移动并返回true
;本质上是同时在正向和逆向迭代,利用递归的特性,可以实现链表逆向迭代,翻转链表的递归方法就是利用这个思想。
-
-
(力扣)328.奇偶链表
-
终止条件
- 当递归到链表只有两个节点时,不需要再进行奇偶分离再合并,递归终止。
-
返回值
- 返回奇链表的尾节点。
-
单次递归过程
- 进行奇偶分离
odd.next = even.next; even.next = odd.next.next;
后,进入下一层递归。
- 进行奇偶分离
-
哑节点
设置dummy
节点,避免对链表第1个节点进行单独讨论。
相关题目
- (力扣)2.两数相加
- (力扣)21.合并两个有序链表
总结
第一次这样写刷题总结,还在摸索当中,凑合看吧,多写几次肯定会有进步,加油!