原文链接:【LeetCode学习计划】《数据结构入门-C++》第7天 链表_Wang_Xin_Ling的博客-CSDN博客
目录
141. 环形链表
LeetCode: 141. 环形链表
题目:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。
分析:
题目分析:1.pos不可以做为参数的意思是,不能用它来判断
2.题目要我们判断是否有环,我们可以用1)哈希表逐一遍历,如果有环状就会重复,这样就判断有环
2)我们可以用龟兔赛跑算法参考: Floyd判圈算法(龟兔赛跑算法)记录_y4ung-CSDN博客_龟兔算法
题目给好的,创建键值对 (因为链表没有键的概念)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
方法一:哈希表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_set<ListNode *> set;
ListNode *p = head;
while(p)
{
if (set.find(p) == set.end())
{
set.insert(p);
}
else
return true;
p = p -> next;
}
return false;
}
};
ListNode* phead=head;
相当于中间储存东西的地方
//不破坏头指针
if (set.find(p) == set.end())
{
set.insert(p);
}如果set中找不到该元素,则增加到set中
else
就是找到了,之前出现过一次,现在又出现一次,证明有环
方法二: 快慢指针
本方法叫做「Floyd 判圈算法」(又称龟兔赛跑算法)
一般来说,我们做题的思路可能是往更少的方向去想,比如更少的遍历次数。而方法2不一样,方法2基于这个事实:进入环之后,就会一直在环中循环。
假设「乌龟」和「兔子」在链表上移动,「乌龟」每次移动一步,「兔子」每次移动两步。如果链表中没有环,那么「兔子」就会一直处于「乌龟」的前方直至链表尾部;如果链表中有环,那么兔子在某个时刻就会到达「乌龟」的后方,我们就是运用这个思路。
但是我们没有办法判断链表中谁在谁的哪里,不过,由于进入环之后将永远处于环中,那么在某个时刻,走一步的「乌龟」和走两步的「兔子」必然会在同一结点相遇,于是我们只要一直进行循环直至两者相遇即可。这也是我们一开始讲的,方法2的思路不像平时那种追求“更少的遍历次数”。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if (!head || !head -> next)
return false;
ListNode *fast = head, *slow = head;
do
{
if(!fast -> next || !fast ->next->next)
return false;
fast = fast->next->next;
slow = slow->next;
}while(fast != slow);
return true;
}
};
先判断是否有2个元素 以及是否满足快慢指针存在条件
do while写法,先进行do后判断
21. 合并两个有序链表
LeetCode
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
方法:递归
如果l1->val < l2->val,那么当前要加入的结点便是l1。我们将l1->next和l2传入递归函数,再将返回值赋给 l1->next
如果l1->val > l2->val,同理由于上述过程中,l1和l2会一直往后移动,因此当中会有一个先为空。假设当前l1==nullptr,那么我们直接将l2连上即可:return l2
/**
* 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* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (!l1)
return l2;
else if(!l2)
return l1;
else if (l1 -> val < l2 -> val)
{
l1 -> next = mergeTwoLists(l1->next,l2);
return l1;
}
else
{
l2->next = mergeTwoLists(l1, l2->next);
return l2;
}
}
};
方法1优化
就如同我们在【LeetCode学习计划】《算法-入门-C++》第8天 广度优先搜索 / 深度优先搜索中提到的问题一样,新链表的所有结点都是l1和l2的结点,如果l1或l2被释放了,那么整个新链表也被破坏了。因此我们新建每一个结点。
新建两个链表中的每一个结点,最后必然会将两个链表全都遍历完,所以最后会出现传入的两个参数都为空指针的情况,这时候我们要单独判断并返回空指针。
class Solution
{
public:
ListNode *mergeTwoLists(ListNode *l1, ListNode *l2)
{
if (!l1 && !l2)
return nullptr;
else if (!l1)
return new ListNode(l2->val, mergeTwoLists(nullptr, l2->next));
else if (!l2)
return new ListNode(l1->val, mergeTwoLists(l1->next, nullptr));
else if (l1->val < l2->val)
return new ListNode(l1->val, mergeTwoLists(l1->next, l2));
else
return new ListNode(l2->val, mergeTwoLists(l1, l2->next));
}
};
方法2:迭代
我们可以使用循环语句来完成。设p1和p2分别指向l1和l2。当p1和p2皆不为空时,比较各结点的值,选取符合条件的结点连上。有一个为空时就跳出循环,然后单独循环p1和p2,其中必然有一个没有遍历完,将它遍历完即可。
注:以下代码新建了每一个结点。可以选择不新建结点
class Solution
{
public:
ListNode *mergeTwoLists(ListNode *l1, ListNode *l2)
{
if (!l1 && !l2)
return nullptr;
ListNode *dummy = new ListNode(-1), *p = dummy;
ListNode *p1 = l1, *p2 = l2;
while (p1 && p2)
{
if (p1->val < p2->val)
{
p->next = new ListNode(p1->val);
p1 = p1->next;
}
else
{
p->next = new ListNode(p2->val);
p2 = p2->next;
}
p = p->next;
}
while (p1)
{
p->next = new ListNode(p1->val);
p1 = p1->next;
p = p->next;
}
while (p2)
{
p->next = new ListNode(p2->val);
p2 = p2->next;
p = p->next;
}
p = dummy->next;
delete dummy;
return p;
}
};
206. 反转链表
LeetCode
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
方法1:迭代 / 双指针
假设存在链表 1 → 2 → 3 → ∅ 1 \我们想要把它改成 ∅ ← 1 ← 2 ← 3 \ \
3∅←1←2←3。
我们在改变当前指针时,需要把它的next指针指向上一个元素,所以我们需要一个额外的指针prev来指向当前指针的上一个元素。由于要往后访问,又要改当前元素的next,所以我们要有一个next变量预存当前的next。
力扣上老哥的视频
/**
* 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* reverseList(ListNode* head) {
ListNode *prev = nullptr, *p = head;
while(p)
{
ListNode *next = p->next;
p->next = prev;
prev = p;
p = next;
}
return prev;
}
};