Skip to content

0024 Solved #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file added 0024-Swap-Nodes-in-Pairs/Animation/Animation1.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 0024-Swap-Nodes-in-Pairs/Animation/Animation2.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
128 changes: 128 additions & 0 deletions 0024-Swap-Nodes-in-Pairs/Article/0024-Swap-Nodes-in-Pairs2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# LeetCode 第 24 号问题:两两交换链表中的节点

> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com

题目来源于 LeetCode 上第 24 号问题:两两交换链表中的节点。

### 题目描述

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

**你不能只是单纯的改变节点内部的值**,而是需要实际的进行节点交换。

**示例:**

```
给定 1->2->3->4, 你应该返回 2->1->4->3.
```

### 题目解析 - 迭代法

由题目描述可知需要两两交换, 那么以两个为一组子链表交换指针即可, 在设置一个 **哨兵** 指向交换后的子链表 (或者哨兵提前指向子链表的第二个节点,因为第二个节点交换后就成了第一个节点); 然后让哨兵指向下一组子链表,继续交换,直至最后.

设 **哨兵** 为 节点 `prev`, 子链表第一个节点为 `A`, 第二个节点为 `B`, 第三个节点为 `C`, 那么操作流程如下:

- 终止条件 `head == null && head -> next == null`
1. prev -> B ( A -> B -> C )
2. A - > C
3. B -> A ( prev -> B -> A -> C )
4. prev -> A
5. head -> C
6. 循环以上步骤

### 动画描述

<img src="../Animation/Animation1.gif" alt="Animation1" style="zoom:150%;" />

### 代码实现

```javascript
/**
* JavaScript描述
* 迭代法
*/
var swapPairs = function(head) {
let dummy = new ListNode(0);
dummy.next = head;

let prevNode = dummy;

while (head !== null && head.next !== null) {
// Nodes to be swapped
let firstNode = head,
secondNode = head.next;
// Swapping
prevNode.next = secondNode; // 放到交换前后都可以
firstNode.next = secondNode.next;
secondNode.next = firstNode;
// Reinitializing the head and prevNode for next swap
prevNode = firstNode;
head = firstNode.next;
}
return dummy.next;
};
```

### 复杂度分析

- 时间复杂度:**O(N)**,其中 *N* 指的是链表的节点数量
- 空间复杂度:**O(1)**

### 题目解析 - 递归

递归的思路和迭代类似, 都是分组交换. 具体来说这里的递归不是针对一个问题深入进去,而是不断向后推进.

- 每次递归只交换一对节点
- 下一次递归则是交换下一对节点
- 交换完成后返回第二个节点, 因为它是交换后的子链表新头
- 递归完成后返回第一次递归的第二个节点, 这就是新链表的头结点

**注意:** 不要人肉递归, 更多关注整体逻辑

示例执行大致流程为:

- 终止条件: `(head == null) || (head.next == null)`
1. 1 -> 2 -> 3 -> 4 ( 原始链表 )
2. 1 -> 3 -> 4
3. ( 2 -> 1 ) -> 3 -> 4 ( 第一次递归完成后返回原来的第二个节点, 也就是值为 2 的节点 )
4. 2 -> 1 -> 3 -> null
5. 2 -> 1 -> ( 4 -> 3 ) ( 第二次递归完成后返回原来的第二个节点, 也就是值为 4 的节点 )

### 动画描述

<img src="../Animation/Animation2.gif" alt="Animation2" style="zoom:150%;" />

### 代码实现

```javascript
/**
* JavaScript描述
* 递归法
*/
var swapPairs = function(head) {
if (head == null || head.next == null) {
return head;
}
// Nodes to be swapped
let firstNode = head,
secondNode = head.next;
// Swapping
firstNode.next = swapPairs(secondNode.next);
secondNode.next = firstNode;

return secondNode;

};
```

### 复杂度分析

- 时间复杂度:**O(N)**,其中 *N* 指的是链表的节点数量
- 空间复杂度:**O(N)**, 递归过程使用的堆栈空间



![](../../Pictures/qrcode.jpg)

Binary file not shown.
Binary file added 0142-Linked-List-Cycle-ii/Animation/Animation.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
176 changes: 176 additions & 0 deletions 0142-Linked-List-Cycle-ii/Article/0142-Linked-List-Cycle-ii.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# LeetCode 第 142 号问题:环形链表 II

> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com

今天分享的题目来源于 LeetCode 上第 142 号问题:环形链表II。题目难度为 Medium 。

### 题目描述

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 `null`。

为了表示给定链表中的环,我们使用整数 `pos` 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 `pos` 是 `-1`,则在该链表中没有环。

**示例 1:**

```
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
```

![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/vweoq.png)

**示例 2:**

```
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。
```

![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/kxbrz.png)

**示例 3:**

```
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。
```

![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/w3vsg.png)

**进阶:**

你是否可以不用额外空间解决此题?

### 题目解析 - 哈希表

普通解法就是利用哈希表保存访问过的节点, 同时遍历过程中检查哈希表中是否已存在相同的节点

### 代码实现

```javascript
/**
* JavaScript 描述
* 哈希表方法
*/
var detectCycle = function(head) {
let res = [ ];
while (head !== null) {
if (res.includes(head)) {
return head;
}
res.push(head);
head = head.next;
}
return null;
};
```

### 复杂度分析

- 时间复杂度:**O(n)**
- 空间复杂度:**O(n)**

### 题目解析 - Floyd 算法

Floyd算法 可以达到常量空间解决此问题.

我在维基百科找到了这个算法描述, 在此引用一下.

**Floyd判圈算法**(**Floyd Cycle Detection Algorithm**),又称 **龟兔赛跑算法**(**Tortoise and Hare Algorithm**),是一个可以在[有限状态机](https://zh.wikipedia.org/wiki/有限状态机)、[迭代函数](https://zh.wikipedia.org/wiki/迭代函数)或者[链表](https://zh.wikipedia.org/wiki/链表)上判断是否存在[环](https://zh.wikipedia.org/wiki/環_(圖論)),求出该环的起点与长度的算法。

如果有限状态机、迭代函数或者链表存在环,那么一定存在一个起点可以到达某个环的某处 ( 这个起点也可以在某个环上 )。

初始状态下,假设已知某个起点节点为节点 *S*。现设两个指针 `t` 和 `h` ,将它们均指向 *S*。

接着,同时让 `t` 和 `h` 往前推进,但是二者的速度不同:`t` 每前进 `1` 步, `h` 前进 `2` 步。只要二者都可以前进而且没有相遇,就如此保持二者的推进。当 `h` 无法前进,即到达某个没有后继的节点时,就可以确定从 *S* 出发不会遇到环。反之当 `t` 与 `h` 再次相遇时,就可以确定从 S 出发一定会进入某个环,设其为环 *C*。

如果确定了存在某个环,就可以求此环的起点与长度。

上述算法刚判断出存在环 *C* 时,显然 t 和 `h` 位于同一节点,设其为节点 *M*。显然,仅需令 `h` 不动,而t不断推进,最终又会返回节点 *M*,统计这一次t推进的步数,显然这就是环 *C* 的长度。

为了求出环 *C* 的起点,只要令h仍均位于节点 *M* ,而令t返回起点节点 *S* ,此时h与t之间距为环 *C* 长度的整数倍。随后,同时让 `t` 和 `h` 往前推进,且保持二者的速度相同:`t` 每前进 `1` 步,`h` 前进 `1` 步。持续该过程直至 `t` 与 `h` 再一次相遇,设此次相遇时位于同一节点 *P*,则节点 *P* 即为从节点 *S* 出发所到达的环 *C* 的第一个节点,即环 *C* 的一个起点。

**看完之后是不是很多疑点, 觉得为什么会这样呢?**

下面用数学简单证明一下

假设 链表的节点数为 `num`, 从 head 到链表环入口节点数为 `m` (不包含入口节点), 环的节点数为 `n`, 链表环入口设点为 *P*

由此可得 `num = m + n`

假设 慢指针 `Tortoise` (乌龟) 每次走 `1` 个节点, 走了 `x` 步

假设 快指针 `Hare` (兔子) 每次走 `2` 个节点, 走了 `f` 步

那么 `f = 2x`

当第一次相遇时, 必然是在环内, 设其点为 *M*, 兔子第一次到达 *M* 点后至少又在环内饶了一圈后追上乌龟,

假设绕了 `k` 圈, 那么可以得到

`f = x + kn`

兔子到达 *P* 点的步数为

`f = m + kn`

由 `f = 2x` 和 `f = x + kn` 两个等式可以得到 `x = kn`

由 `f = m + kn` 和 `x = kn` 可知, 乌龟到达 *P* 点还需要走 `m` 步

而 `m` 的长度正是从 head 到链表环入口节点数的长度, 这是未知的,

那么让兔子从 head 以乌龟的速度走, 乌龟在 *M* 点走, 当兔子和乌龟相遇时即走了 `m` 步, 也就到达了 *P* 节点.

### 动画描述

![](../Animation/Animation.gif)

### 代码实现

```java
/**
* JavaScript 描述
* Floyd判圈算法
*/
var detectCycle = function(head) {
if (head == null) {
return head;
}
// 设置快慢指针
let tortoise = head,
hare = head;
// 检查链表是否有环
while (true) {
if (hare == null || hare.next == null) {
return null;
}
hare = hare.next.next;
tortoise = tortoise.next;
if (hare == tortoise) {
break;
}
}
// 兔子和乌龟第二次相遇找到环入口
hare = head;
while (hare != tortoise) {
hare = hare.next;
tortoise = tortoise.next;
}
return hare;
};
```

### 复杂度分析

- 时间复杂度:**O(n)**
- 有环情况下, 第一次和第二次相遇, 乌龟步数都小于链表节点数, 因此与链表节点数成线性关系;
- 无环情况下, 兔子大约需要 n/2 步数到达最后, 因此也与链表节点数成线性关系.
- 空间复杂度:**O(1)** , 双指针使用常数大小的额外空间

![](../../Pictures/qrcode.jpg)