链表(LinkedList)
高频面试题
1.反转链表
🚀题目链接:LeetCode206.反转链表
题目:
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
🍬C++ AC代码:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// curr:当前遍历到的节点;prev:当前遍历节点的前一个节点
ListNode *curr = head, *prev = nullptr;
while(curr != nullptr) {
// 等下要修改curr.next指针,这里先将curr指向的节点的下一个节点保存下来
ListNode *next = curr->next;
// 将curr.next指向它的前一个节点
curr->next = prev;
// 将prev与curr指针分别向后移动
prev = curr;
curr = next;
}
// 遍历结束后prev是原链表的最后一个节点,也就是新链表的头节点。
return prev;
}
};
✨Tips:
- ⭐题目比较简单,处理好每个节点的指针即可。
☕Java AC代码:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode curr = head, prev = null;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
}
🍦Python AC代码:
class Solution(object):
def reverseList(self, head):
curr, prev = head, None
while curr is not None:
curr.next, prev, curr = prev, curr, curr.next
return prev
✨Tips:
- ⭐
curr.next, prev, curr = prev, curr, curr.next
这个是Python中的一次性赋值。而Python中的一次性赋值会将=
右边的所有值先计算出来并保存在临时变量中,之后再依次赋值给=
左边。 - ⭐这种一次性赋值的好处是不需要像C++或者Java代码那样每次循环时先创建一个临时变量来保存下一个节点,Python的一次性赋值语法自动把这个工作完成了。
2.环形链表
🚀题目链接:LeetCode141.环形链表
题目:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。
进阶:你是否可以使用 O(1) 空间解决此题?
🍬C++ AC代码:
class Solution {
public:
bool hasCycle(ListNode *head) {
// 设置一对快慢指针
ListNode *slow = head, *fast = head;
// 慢指针每次走一步,快指针每次走两步
while (slow != nullptr && fast != nullptr) {
slow = slow->next;
// 快指针走两步要提前判一下空指针,防止报错
if (fast->next == nullptr)
return false;
fast = fast->next->next;
// 快慢指针相遇,说明有环
if (slow == fast)
return true;
}
return false;
}
};
✨Tips:
- ⭐注意快指针每次走两步,如果快指针刚好是尾节点,走一步会指向null值,再走一部则会报错,因此要提前判断空指针异常。
☕Java AC代码:
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head, fast = head;
while (slow != null && fast != null) {
slow = slow.next;
if (fast.next == null)
return false;
fast = fast.next.next;
if (slow == fast)
return true;
}
return false;
}
}
🍦Python AC代码:
class Solution(object):
def hasCycle(self, head):
slow, fast = head, head
while slow and fast:
if fast.next is None:
return False
slow, fast = slow.next, fast.next.next
if slow == fast:
return True
return False
✨Tips:
- ⭐借助Python中的一次性赋值可以合并代码,比如:
slow, fast = slow.next, fast.next.next
。
3.环形链表-II
🚀题目链接:LeetCode142.环形链表-II
题目:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
进阶:你是否可以使用 O(1) 空间解决此题?
🍬C++ AC代码:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *slow = head, *fast = head;
while (slow != nullptr && fast != nullptr) {
slow = slow->next;
if (fast->next == nullptr)
return nullptr;
fast = fast->next->next;
// 发现存在环
if (slow == fast) {
// slow指针与fast指针相遇,让slow和head指针同时向前走直到相遇
while (head != slow) {
head = head->next;
slow = slow->next;
}
// 结束位置就是环的起点
return head;
}
}
return nullptr;
}
};
✨Tips:
- ⭐本题难点是如何找到环的起点,但是体现再代码上并不难,当快慢指针相遇时,让head指针和慢指针同时向前走,这两个指针再相遇就是环的起点。
☕Java AC代码:
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head, fast = head;
while (slow != null && fast != null) {
slow = slow.next;
if (fast.next == null)
return null;
fast = fast.next.next;
if (slow == fast) {
while (head != slow) {
head = head.next;
slow = slow.next;
}
return head;
}
}
return null;
}
}
🍦Python AC代码:
class Solution(object):
def detectCycle(self, head):
slow, fast = head, head;
while slow and fast:
slow = slow.next;
if fast.next is None:
return None
fast = fast.next.next
if slow == fast:
while head != slow:
head, slow = head.next, slow.next;
return head
return None
4.两两交换链表中的节点
🚀题目链接:LeetCode24. 两两交换链表中的节点
题目:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4] 输出:[2,1,4,3]
🍬C++ AC代码:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
// 对于空链表和只有一个节点的链表直接返回即可
if (head == nullptr || head->next == nullptr)
return head;
// 在头节点之前增加一个节点,便于节点的交换操作
ListNode *temp_node = new ListNode(0, head);
// 交换链表时需要修改的位置
ListNode *prev = temp_node, *p1 = head, *p2 = head->next;
while (p1 != nullptr && p2 != nullptr) {
// 两两交换
p1->next = p2->next;
p2->next = p1;
prev->next = p2;
// 让prev,p1,p2指向下一组待操作的节点
prev = p1;
p1 = p1->next;
if (p1 != nullptr)
p2 = p1->next;
}
return temp_node->next;
}
};
✨Tips:
- ⭐在头节点前增加一个节点的操作后面的题目中还会用到,很巧妙的方法,原链表的顺序改变以后我们可以利用新增加的这个节点快速定位到最终要返回的节点:
return temp_node->next;
。 - ⭐本题重点在于处理好两个节点间与它们的前一个和后一个节点的指向关系,节点的两两交换只是
next
指针域改变了,节点本身在内存中的位置并没有改变。
☕Java AC代码:
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null)
return head;
ListNode tempNode = new ListNode(0, head);
ListNode prev = tempNode, p1 = head, p2 = head.next;
while (p1 != null && p2 != null) {
p1.next = p2.next;
p2.next = p1;
prev.next = p2;
prev = p1;
p1 = p1.next;
if (p1 != null)
p2 = p1.next;
}
return tempNode.next;
}
}
🍦Python AC代码:
class Solution(object):
def swapPairs(self, head):
if head == None or head.next == None:
return head
tempNode = ListNode(0, head)
prev, p1, p2 = tempNode, head, head.next
while p1 and p2:
prev.next, p1.next, p2.next = p2, p2.next, p1
prev, p1 = p1, p1.next
if p1 is not None:
p2 = p1.next
return tempNode.next
5.K个一组翻转链表
🚀题目链接:LeetCode25. K 个一组翻转链表
题目:
给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那
请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。示例 1:
输入:head = [1,2,3,4,5], k = 2 输出:[2,1,4,3,5]
🍬C++ AC代码:
class Solution {
public:
// 翻转子链表,并返回新的子链表头节点和尾节点
pair<ListNode*, ListNode*> myReverse(ListNode* head, ListNode* tail) {
// 这里让初始的prev节点指向子链表的下一个节点,那么反转以后的子链表会直接和子链表后面的部分接上
ListNode *prev = tail->next, *curr = head;
while(curr != tail) {
ListNode *next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
curr->next = prev;
// 翻转以后子链表的头和尾颠倒了位置,因此调换两个节点的顺序
return {tail, head};
}
ListNode* reverseKGroup(ListNode* head, int k) {
// 创建一个新的头节点用以简化代码
ListNode* tempNode = new ListNode(0, head);
// 在翻转子链表的函数中已经将子链表和后面的部分相接,这里还需要一个指针保存前面的部分
ListNode* prev = tempNode;
while (head != nullptr) {
// head与tail分别指向待翻转子链表的头与尾
ListNode* tail = prev;
// 判断待翻转的长度是否大于k
for (int i = 0; i < k; i++) {
tail = tail->next;
if (tail == nullptr)
return tempNode->next;
}
// 将整个链表的前半部分与翻转后的子链表相接
pair<ListNode*, ListNode*> child_list = myReverse(head, tail);
prev->next = child_list.first;
// 修改prev与head指针,准备翻转下一个子链表
prev = child_list.second;
head = prev->next;
}
return tempNode->next;
}
};
✨Tips:
- ⭐这里的代码参考了力扣的官方题解,并去掉了官方题解的冗余代码。在
myReverse()
方法中已经将反转后的子链表和原链表的后面的部分相连,因此在reverseKGroup()
中就不用考虑这个问题了。 - ⭐
myReverse()
方法要返回子链表的头节点和尾节点,可以把这两个节点作为一个二元组返回。C++中已经实现了二元组这种数据类型,用pair<T, T>
表示,使用时用pair.first
与pair.second
访问其中的两个元素。
☕Java AC代码:
class Solution {
public ListNode[] myReverse(ListNode head, ListNode tail) {
ListNode prev = tail.next;
ListNode curr = head;
while (curr != tail) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
curr.next = prev;
return new ListNode[] {tail, head};
}
public ListNode reverseKGroup(ListNode head, int k) {
ListNode tempNode = new ListNode(0, head);
ListNode prev = tempNode;
while (head != null) {
ListNode tail = prev;
for (int i = 0; i < k; i++) {
tail = tail.next;
if (tail == null)
return tempNode.next;
}
ListNode[] chileList = myReverse(head, tail);
prev.next = chileList[0];
prev = chileList[1];
head = prev.next;
}
return tempNode.next;
}
}
✨Tips:
- ⭐Java中没有二元组这种数据结构,可以使用一个数组代替。
🍦Python AC代码:
class Solution(object):
def myReverse(self, head, tail):
prev = tail.next
curr = head
while curr != tail:
next = curr.next
curr.next = prev
prev = curr
curr = next
curr.next = prev
return tail, head
def reverseKGroup(self, head, k):
tempNode = ListNode(0, head)
prev = tempNode
while head is not None:
tail = prev
for i in range(k):
tail = tail.next;
if tail is None:
return tempNode.next
head, tail = self.myReverse(head, tail)
prev.next = head
prev = tail
head = tail.next
return tempNode.next
✨Tips:
- ⭐Python中允许我们在函数中返回多个值:
head, tail = self.myReverse(head, tail)
,非常方便。