做了一段时间的链表题,多多少少也看了一些优秀题解,链表解题技巧无非就以下几种:
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
正好走到中间,完美解决题目。fast
的速度是slow
的两倍,所以当fast
走到末尾时,slow
正好在中间。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个升序链表
(力扣)24.两两交换链表中的节点
终止条件
返回值
单次递归过程
将待交换的两个节点设置为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.反转链表
递归边界
返回值
本级任务
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个节点进行单独讨论。
相关题目
第一次这样写刷题总结,还在摸索当中,凑合看吧,多写几次肯定会有进步,加油!
原文:https://www.cnblogs.com/ly-leah/p/13700622.html