题目1 24. 两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
提示:
- 链表中节点的数目在范围
[0, 100]
内 0 <= Node.val <= 100
思路
无虚拟头
这道题中每次交换两个节点时要改变三个指针的指向,所以我们需要三个变量来记录这三个相邻节点。在第一次交换节点(在满足节点数量>=2)时需要注意只需要更改头节点和后一个节点就行了,第一次交换节点只需要两个节点,之后的交换才需要三个节点。
代码
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head == nullptr)
{
return head;
}
ListNode* preNode = head;
//如果节点有两个以上,则移动头指针到第二个节点
if(head->next != nullptr)
{
head = head->next;
}
else
{
return head;
}
ListNode* ppreNode = nullptr;
ListNode* curNode = preNode->next;
for(int i = 0; curNode != nullptr; i++)
{
//i % 2 == 0判断表示隔一个节点后进行两两交换
if(i % 2 == 0)
{
ListNode* tmpNode = curNode->next;
curNode->next = preNode;
preNode->next = tmpNode;
if(ppreNode != nullptr)
ppreNode->next = curNode;
curNode = tmpNode;
}
else
{
ppreNode = preNode;
preNode = curNode;
curNode = curNode->next;
}
}
return head;
}
};
有虚拟头
有虚拟头的情况比无虚拟头简单,因为不用额外考虑第一次只修改两次指针的情况,所有交换的情况都是三个指针进行操作。要注意的就是在实际项目中要在返回前释放掉虚拟头节点的内存,避免内存泄漏。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head == nullptr || head->next == nullptr)
{
return head;
}
ListNode* dumNode = new ListNode(-1, head);
ListNode* ppreNode = dumNode,
* preNode = dumNode->next,
* curNode = preNode->next;
while(curNode != nullptr)
{
ppreNode->next = curNode;
ListNode* tmpNode = curNode->next;
curNode->next = preNode;
preNode->next = tmpNode;
if(tmpNode != nullptr && tmpNode->next != nullptr)
{
ppreNode = preNode;
preNode = tmpNode;
curNode = tmpNode->next;
}
else
{
break;
}
}
head = dumNode->next;
delete dumNode;
return head;
}
};
题目2 19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 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
思路
暴力迭代法(两轮遍历)
暴力迭代法通过遍历两轮链表来删除倒数第n个节点,第一轮用来计算链表的节点总数,第二轮用于命中目标节点与前一个节点来进行删除操作。
额外指针数组(一轮遍历)
因为题目中给出了结点的数量范围,因此创建一个固定大小的listNode指针数组,用一轮遍历统计结点总数并将结点按序放入到数组中。之后通过计算能命中倒数第n个节点的位置,最终通过指针数组来删除指针。
代码
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head == nullptr)
{
return head;
}
ListNode* curNode = new ListNode(0, head);
ListNode* lst[32];
lst[0] = curNode;
int num = 0;
for(; curNode != nullptr; num++)
{
curNode = curNode->next;
lst[num + 1] = curNode;
}
//goal + n = num - > goal = num - n
//num - n为要删除的节点位置,对其-1和+1为前和后结点
lst[num - n - 1]->next = lst[num - n + 1];
delete lst[num - n];
head = lst[0]->next;
delete lst[0];
return head;
}
};
双指针法
假设链表长度是n(n > 0),要删除的结点时倒数第y( 0 < y <= n )个,左指针的位置为lft,右指针的位置为rht,在链表前用额外的结点(dummyNode)指向链表头。让lft和rht的初始位置都为dummyNode,先让rht向后面的结点移动y次,此时lft和rht的正好相差y个结点。之后同时让lft和rht向后面结点移动,直到rht指向为空终止,此时lft对应的指针指向的正好是倒数第y个结点。因此如果想删除第n个结点就需要倒数第n+1个结点的位置,代码也因此能写出来。
代码
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyNode = new ListNode(0, head);
ListNode* lft = dummyNode,
* rht = dummyNode;
for(int i = 0; i <= n; i++)
{
rht = rht->next;
}
while(rht != nullptr)
{
lft = lft->next;
rht = rht->next;
}
lft->next = lft->next->next;
head = dummyNode->next;
delete dummyNode;
return head;
}
};
题目3 07. 链表相交
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。
提示:
listA
中节点数目为m
listB
中节点数目为n
0 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
- 如果
listA
和listB
没有交点,intersectVal
为0
- 如果
listA
和listB
有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]
思路
这道题只要将两个链表的尾部对齐就好做了,首先判断两个链表的长短并在第一次遍历结束后通过最后一个结点判断两链表是否相交,若相交则用指针指向长链表的子链表结点,使得子链表长度和另一个短链表长度一致,最后一一比较就找到了相交的结点。
代码
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA == nullptr || headB == nullptr)
{
return nullptr;
}
ListNode* aNode = headA,
* bNode = headB;
int lenA = 0, lenB = 0;
while(aNode->next != nullptr)
{
aNode = aNode->next;
lenA++;
}
while(bNode->next != nullptr)
{
bNode = bNode->next;
lenB++;
}
if(aNode != bNode)
{
return nullptr;
}
int len;
#define FIND_UNODE(LLIST, SLIST, LLEN, SLEN, LEN)\
LEN = LLEN - SLEN; \
while(LEN--){ \
LLIST = LLIST->next; \
} \
while(LLIST != SLIST){ \
LLIST= LLIST->next; \
SLIST = SLIST->next; \
}
if(lenA > lenB)
{
FIND_UNODE(headA, headB, lenA, lenB, len);
}
else
{
FIND_UNODE(headB, headA, lenB, lenA, len);
}
#undef FIND_UNODE
return headA;
}
};
题目4 142. 环形链表 II
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
提示:
- 链表中节点的数目范围在范围
[0, 104]
内 -105 <= Node.val <= 105
pos
的值为-1
或者链表中的一个有效索引
进阶:你是否可以使用 O(1)
空间解决此题?
思路
双指针
这道题更像是数学计算题。。。利用双指针中快指针的一次多步和慢指针的一次一步来遍历整个链表,直到两个指针相遇,最后在通过新指针和快指针的进一步遍历及相遇来确定环的入口。
代码
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == nullptr)
{
return head;
}
ListNode* fast = head,
* slow = head;
while(fast->next != nullptr && fast->next->next != nullptr)
{
fast = fast->next->next;
slow = slow->next;
if(slow == fast)
{
ListNode* markNode = head;
while(markNode != fast)
{
markNode = markNode->next;
fast = fast->next;
}
return markNode;
}
}
return nullptr;
}
};
set集合筛选法
众所周知,在C++里有set模板类可以筛除重复的元素,我们用这个类保存链表里的所有结点,并对每一次插值前和插值后比较set对象的总数,若set对象总数没发生变化就说明已经进入环入口。使用这种方法要额外消耗内存,但在接近最坏的情况时(即链表非常长,环也很大的情况下,用这种方法能节省最后双指针法寻找遍历的时间)。
代码
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == nullptr)
{
return head;
}
set<ListNode*> roundSet;
int preNum = INT_MAX;
while(head != nullptr)
{
preNum = roundSet.size();
roundSet.insert(head);
if(preNum == roundSet.size())
break;
head = head->next;
}
return head;
}
};
标签:head,ListNode,nullptr,day4,随想录,next,链表,节点
From: https://www.cnblogs.com/code4log/p/18389679