1、链表的中间节点
876. 链表的中间结点
难度简单479收藏分享切换为英文接收动态反馈
给定一个头结点为 head
的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
注意,我们返回了一个 ListNode 类型的对象 ans,这样:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
提示:
- 给定链表的结点数介于
1
和100
之间。
题解:
- 数组法:链表的缺点在于不能通过下标访问对应的元素。遍历链表,同时将遍历到的元素依次放入数组 A 中。如果我们遍历到了 N 个元素,那么链表以及数组的长度也为 N,对应的中间节点即为 A[N/2]
- 单指针,遍历两边链表,第一次遍历时,我们统计链表中的元素个数 N;第二次遍历时,我们遍历到第 N/2 个元素时,将该元素返回即可
- 经典做法:利用快慢双指针,当快指针走两步,慢指针走一步,而快指针满足当前非空和下一个节点非空时可前进,否则停下,此时即慢指针刚刚好到达中间位置或者偶数的中间位置的第二个数字
图解如下:
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
//双指针法,利用快慢指针,快指针走两步,慢指针走一步
//当前进需要满足条件:快指针当前和快指针的下一个非空
//若最后不满足条件停下:即慢指针刚刚好到中间位置,偶数则为中间位置的第二个数字
ListNode* middleNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
2、删除链表倒数第N个结点
19. 删除链表的倒数第 N 个结点
难度中等1764收藏分享切换为英文接收动态反馈
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
提示:
- 链表中结点的数目为
sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
**进阶:**你能尝试使用一趟扫描实现吗?解: 递归思想
**题解:**首先本体因为需要对头结点进行返回,一般常用的技巧是添加一个哑节点(dummy node),它的next 指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了。
-
添加哑结点,很容易就想到第一种解法就是将链表进行扫一遍,并且记录长度L,接着再从头遍历,当遍历到L-N+1个节点时(添加了哑节点),它的下一个节点便是我们需要删除的,然后将其删除即可(将其next指向下下个节点)。
-
快慢双指针法,添加哑结点,然后创建快慢指针,由于我们需要找到倒数第 nn 个节点,因此我们可以使用两个指针 fast和slow同时对链表进行遍历,并且fast比 slow超前 n 个节点。当fast 遍历到链表的末尾时,slow就恰好处于倒数第 n 个节点。
具体地,初始时 fast指向头节点,slow指向新的虚拟哑结点。我们首先使用fast对链表进行遍历,遍历的次数为 n(与慢指针间隔为n个结点)。此时,fast 和slow之间间隔了 n 个节点,即 fast 比 slow超前了 n 个节点。
在这之后,我们同时使用fast 和slow对链表进行遍历。当 slow遍历到链表的末尾(即 fast为空指针)时,slow恰好指向倒数第 n -1个节点(即所需要删除结点的前结点),然后将其指针指向下下个结点即可。
具体可如图:
代码如下:
方法一:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
int Length=0;
ListNode* dummy = new ListNode(0, head);//创建一个虚拟的头节点,dummy的下一个结点就是head
ListNode* begin=head;
ListNode* end=dummy;
while(begin!=NULL){
begin=begin->next;
Length++;
}
int flag=Length-n;
for(int i=0;i<flag;i++)
end=end->next;
end->next=end->next->next;//到达所需要删除结点的前节点,将其指针指向下下个结点
return dummy->next;//返回头指针
}
};
方法二:
class Solution {
public:
ListNode *removeNthFromEnd(ListNode *head, int n) {
ListNode *dummy = new ListNode(0, head);//创建一个虚拟的头节点,dummy的下一个结点就是head
ListNode *fast = head;
ListNode *slow = dummy;
for (int i = 0;i<n;i++){
fast = fast->next;
} // 此时 slow 与 fast 之间距离为 n + 2
while (fast) {
fast = fast->next;
slow = slow->next;
} // 此时 fast 为原链表后的 slow 指向删除节点的前一个
slow->next = slow->next->next;
return dummy->next;
}
};
解题总结:这种双指针,一般注意快慢指针,并且注意间隔。这种倒数第几个甚至找中间结点都需要快走几步或者间隔多少个,往往快指针到尾部了,慢指针就到合适地方。