链表面试专题总结:你真的会反转链表么?
链表类型的题目,一直以来是面试必考的题型。考查的核心有两点:
- 一、思路的考察,如快慢双指针、递归反转、环的入口等。
- 二、对指针的处理细节,如头尾节点、反转节点指针等
往往只有两者结合,才能正确解决链表问题。否则容易出现没有思路,或者有了思路但却不知道如何正确处理指针走向,导致解题失败。
在链表面试题中,反转链表更是一个热门+必问考点。全链表反转属于基础,由此延伸出的问题更是五花八门。对于这类问题,如果没有正确解答,往往宣告面试的结束。
本专题总结了链表反转的六大题型,希望大家看完之后,都可以轻松解决面试中遇到的链表问题,拿到心仪的offer。
反转链表相关题目列表:
1. 反转整个链表
2. 反转链表前n个节点
3. 反转链表后n个节点
4. 反转部分链表
5. 反转相邻链表
6. 反转链表k个一组
7. 判断链表是否回文结构
1.反转整个链表
非递归方式利用了pre和next指针,保存前驱节点用于反转,后继节点用于循环。
反转循环过程核心为三步:
- 保存后继节点
- 反转当前节点
- 指针后移继续循环
/**
* 1.反转整个链表 非递归
*/
public ListNode reverseAllList(ListNode head) {
ListNode pre = null;
ListNode next;
while (head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
链表反转递归方式为经典的体现递归题目。要明确递归反转的方法含义,即反转head为头节点的链表,返回反转之后的头节点。回到方法中,此时last节点指向的,是以head.next为头节点的链表,反转之后的头节点。然后进行指针的反转,并且末尾指向NULL。注意递归的终止条件为head.next == null
,但是防止入参为空,所以需要加上head == null
判断。
/**
* 1.反转整个链表 递归
*/
public ListNode reverseAllListRecur(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode last = reverseAllListRecur(head.next);
head.next.next = head;
head.next = null;
return last;
}
2.反转链表前n个节点
非递归方法:理解了全链表反转,该题只需要注意指针的处理。反转前n个节点,需要保存第n+1个节点,与反转后的前N个节点链表进行拼接。
/**
* 2.反转链表前n个节点 非递归
*/
public ListNode reverseFirstN(ListNode head, int n) {
ListNode nNext = head;
for (int i = 0; i < n; i++) {
nNext = nNext.next;
}
ListNode pre = null;
ListNode next;
ListNode cur = head;
while (cur != nNext) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
head.next = nNext;
return pre;
}
递归方法类似全链表反转,注意递归的终止条件
ListNode nNext = null; // 后驱节点
/**
* 2.反转链表前n个节点 递归
*/
public ListNode reverseFirstNRecur(ListNode head, int n) {
// n==1则走到了第n个节点,找到第n+1个节点
if (n == 1) {
nNext = head.next;
return head;
}
ListNode last = reverseFirstNRecur(head.next, n - 1);
head.next.next = head;
head.next = nNext;
return last;
}
3.反转链表后n个节点
public ListNode reverseLastN(ListNode head, int n) {
ListNode slow = head;
ListNode fast = head;
for (int i = 0; i < n; i++) {
fast = fast.next;
}
while (fast.next != null) {
slow = slow.next;
fast = fast.next;
}
ListNode cur = slow.next;
ListNode pre = null;
ListNode next;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
slow.next = pre;
return head;
}
4.反转部分链表
反转部分链表,从第from个节点到第to个节点,则需要有指向第from-1个节点的指针startPre,指向to节点的指针end。而我们需要的start节点和to的后节点,均可通过两者后移得到。
注意: from是1的情况,此时返回的头节点不再是原来的头节点,而是反转后的头节点。
/**
* 4.反转链表其中一部分 从第from个节点到第to个节点的部分
*/
public ListNode reversePart(ListNode head, int from, int to) {
int len = 0;
ListNode cur = head;
ListNode startPre = null;
ListNode end = null;
while (cur != null) {
len++;
if (len == from - 1) {
startPre = cur;
}
if (len == to) {
end = cur;
}
cur = cur.next;
}
//考虑from是1的情况
ListNode start = startPre == null ? head : startPre.next;
ListNode endNext = end.next;
cur = start;
ListNode pre = null;
ListNode next;
while (cur != endNext) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
if (startPre != null) {
startPre.next = pre;
} else {
head = pre;
}
start.next = endNext;
return head;
}
5.反转相邻链表
在头节点前设置前驱节点dummy,每次循环取得cur节点的后两个节点,设为p、q,反转pq的指针。注意调整的顺序。
/**
* 5.反转链表相邻节点 非递归
*/
public ListNode reverseInPairs(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode cur = dummy;
while (cur.next != null && cur.next.next != null) {
ListNode p = cur.next;
ListNode q = cur.next.next;
cur.next = q;
p.next = q.next;
q.next = p;
cur = p;
}
return dummy.next;
}
/**
* 5.反转链表相邻节点 递归
*/
public ListNode reverseInPairsRecur(ListNode head) {
if ((head == null) || (head.next == null)) {
return head;
}
ListNode next = head.next;
head.next = reverseInPairsRecur(head.next.next);
next.next = head;
return next;
}
6.K个一组反转链表
结合全链表反转,将链表分成K个一组进行全链表反转,反转完成后再拼接回原链表中,继续下一轮循环。
/**
* 6.k个一组反转链表
*/
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
ListNode end = dummy;
//k个为一组,分为子链表。pre记录子链表的头节点的前驱节点,end记录子链表尾节点
while (end.next != null) {
for (int i = 0; i < k && end != null; i++) {
end = end.next;
}
if (end == null) {
break;
}
ListNode start = pre.next;
ListNode endNext = end.next;
end.next = null;
pre.next = reverseAllList(start);
//重新续上反转后的子链表
//start此时反转后,变成尾节点
start.next = endNext;
pre = start;
end = start;
}
return dummy.next;
}
7.判断链表是否回文结构
反转链表后半部分,与前半部分链表逐一比较判断是否回文。判断完成后再将后半部分链表反转回去
/**
* 7.判断链表是否回文结构
*/
public boolean isPalindrome(ListNode head) {
if (head == null || head.next == null) {
return true;
}
ListNode fast = head;
ListNode slow = head;
// 根据快慢指针,找到链表的中点
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
boolean result = true;
ListNode rHead = reverseAllList(slow.next);
ListNode cur = rHead;
while (cur != null) {
if (head.val != cur.val) {
result = false;
break;
}
head = head.next;
cur = cur.next;
}
//将后半部分链表反转回来,并拼接
slow.next = reverseAllList(rHead);
return result;
}