LeetCode【学习计划】:【数据结构】
141. 环形链表
LeetCode: 141. 环形链表
简 单 \color{#00AF9B}{简单} 简单
给你一个链表的头节点
head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果pos
是-1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回true
。 否则,返回false
。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
提示:
- 链表中节点的数目范围是 [0, 104]
- -105 <= Node.val <= 105
pos
为-1
或者链表中的一个 有效索引 。
进阶:你能用 O(1)
(即,常量)内存解决此问题吗?
方法1:哈希表
像这种判断是否曾经访问过的情况,基本上都可以使用哈希表去做。本题中我们可以在哈希表中存储已遍历的结点地址。如果遍历时当前结点存在于哈希表中,就代表我们进入了一个环,返回真值。
#include <unordered_set>
using namespace std;
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;
}
};
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n)
-
空间复杂度: O ( n ) O(n) O(n)。主要为哈希表的开销。
参考结果
Accepted
20/20 cases passed (20 ms)
Your runtime beats 12.69 % of cpp submissions
Your memory usage beats 8.23 % of cpp submissions (10.4 MB)
方法2:快慢双指针
本方法叫做「Floyd 判圈算法」(又称龟兔赛跑算法)
一般来说,我们做题的思路可能是往更少的方向去想,比如更少的遍历次数。而方法2不一样,方法2基于这个事实:进入环之后,就会一直在环中循环。
假设「乌龟」和「兔子」在链表上移动,「乌龟」每次移动一步,「兔子」每次移动两步。如果链表中没有环,那么「兔子」就会一直处于「乌龟」的前方直至链表尾部;如果链表中有环,那么兔子在某个时刻就会到达「乌龟」的后方,我们就是运用这个思路。
但是我们没有办法判断链表中谁在谁的哪里,不过,由于进入环之后将永远处于环中,那么在某个时刻,走一步的「乌龟」和走两步的「兔子」必然会在同一结点相遇,于是我们只要一直进行循环直至两者相遇即可。这也是我们一开始讲的,方法2的思路不像平时那种追求“更少的遍历次数”。
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;
}
};
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n)
-
空间复杂度: O ( 1 ) O(1) O(1)。我们只需要常量空间来存储若干变量。
参考结果
Accepted
20/20 cases passed (4 ms)
Your runtime beats 99.66 % of cpp submissions
Your memory usage beats 66.05 % of cpp submissions (7.9 MB)
21. 合并两个有序链表
LeetCode: 21. 合并两个有序链表
简 单 \color{#00AF9B}{简单} 简单
203. 移除链表元素
LeetCode: 203. 移除链表元素
简 单 \color{#00AF9B}{简单} 简单
给你一个链表的头节点
head
和一个整数val
,请你删除链表中所有满足Node.val == val
的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
示例 2:
输入:head = [], val = 1
输出:[]
示例 3:
输入:head = [7,7,7,7], val = 7
输出:[]
提示:
- 列表中的节点数目在范围 [0, 104] 内
- 1 <= Node.val <= 50
- 0 <= val <= 50
方法1:迭代
本体就是正常的遍历链表并删除结点。本题中链表为单向链表,因此我们需要记录当前结点的前一个结点,像双指针一样,每次两个指针均往后走一步进行遍历。
class Solution
{
public:
ListNode *removeElements(ListNode *head, int val)
{
ListNode dummy{0, head};
ListNode *parent = &dummy, *p = parent->next;
while (p)
{
if (p->val == val)
{
ListNode *target = p;
parent->next = p->next;
p = parent->next;
delete target;
}
else
{
parent = p;
p = p->next;
}
}
return dummy.next;
}
};
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n)
-
空间复杂度: O ( 1 ) O(1) O(1)。我们只需要常量空间来存储若干变量。
参考结果
Accepted
66/66 cases passed (16 ms)
Your runtime beats 95.2 % of cpp submissions
Your memory usage beats 61.92 % of cpp submissions (14.6 MB)