LeetCode 算 法 实 战 - - - 移 除 链 表 元 素、反 转 链 表
- [第 一 题 - - - 移 除 链 表 元 素](#第 一 题 - - - 移 除 链 表 元 素)
-
- [方 法 一 - - - 原 地 删 除](#方 法 一 - - - 原 地 删 除)
- [方 法 二 - - - 双 指 针](#方 法 二 - - - 双 指 针)
- [方 法 三 - - - 尾 插](#方 法 三 - - - 尾 插)
- [第 二 题 - - - 反 转 链 表](#第 二 题 - - - 反 转 链 表)
-
- [方 法 一 - - - 迭 代](#方 法 一 - - - 迭 代)
- [方 法 二 - - - 采 用 头 插 创 建 新 链 表](#方 法 二 - - - 采 用 头 插 创 建 新 链 表)
- [总 结](#总 结)
💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 数据 结 构。
💡个 人 主 页:@笑口常开xpr 的 个 人 主 页
📚系 列 专 栏:硬 核 数 据 结 构 与 算 法
✨代 码 趣 语:恰 当 的 数 据 视 图 实 际 上 就 决 定 了 程 序 的 结 构。
💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。
📦gitee 链 接:gitee

在 数 据 结 构 的 世 界 里,每 一 种 设 计 都 可 能 孕 育 出 惊 人 的 效 率 变 革。你 是 否 深 思 过,一 组 精 心 组 织 的 数 据 究 竟 能 创 造 怎 样 的 奇 迹?每 一 次 挖 掘 底 层 原 理,都 是 与 计 算 机 智 慧 的 巅 峰 对 话;每 一 次 剖 析 存 储 模 式,都 在 破 解 数 据 世 界 的 终 极 密 码。准 备 好 迎 接 这 场 盛 宴 了 吗?让 我 们 一 同 探 寻 双 指 针 的 无 尽 奥 秘,见 证 它 如 何 重 塑 数 字 时 代 的 运 行 法 则!
第 一 题 - - - 移 除 链 表 元 素
描 述:给 你 一 个 链 表 的 头 节 点 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
方 法 一 - - - 原 地 删 除
思 路 分 析
要 删 除 链 表 中 值 为 val 的 节 点,需 要 考 虑 两 种 情 况:一 是 头 节 点 的 值 恰 好 为 val;二 是 中 间 或 尾 部 节 点 的 值 为 val。对 于 头 节 点,直 接 删 除 即 可,不 过 要 注 意 更 新 头 节 点;对 于 中 间 或 尾 部 节 点,需 要 借 助 前 驱 节 点 来 删 除 当 前 节 点。
解 题 步 骤
删 除 头 部 的 目 标 节 点 :
(1)利 用 循 环 判 断 头 节 点 是 否 存 在,并 且 其 值 是 否 等 于 val。
(2)若 满 足 条 件,就 临 时 保 存 当 前 头 节 点,将 头 节 点 更 新 为 下 一 个 节 点, 然 后 释 放 临 时 保 存 的 头 节 点。
删 除 中 间 和 尾 部 的 目 标 节 点 :
(1)初 始 化 temp 指 针,让 它 指 向 新 的 头 节 点(可 能 是 原 头 节 点,也 可 能 是 更 新 后 的 头 节 点)。
(2)遍 历 链 表,确 保 当 前 节 点 temp 和 它 的 下 一 个 节 点 都 不 为 空。
(3)若 下 一 个 节 点 的 值 等 于 val,则 临 时 保 存 该 节 点,将 当 前 节 点 的 next 指 针 指 向 下 下 个 节 点,最 后 释 放 临 时 保 存 的 节 点。
(4)若 下 一 个 节 点 的 值 不 等 于 val,则 将 temp 指 针 后 移 一 位。
返 回 新 的 头 节 点 :
遍 历 结 束 后,返 回 更 新 后 的 头 节 点 head。
温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。
下 面 展 示代 码 示 例
。
javascript
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val)
{
while (head != NULL && head->val == val)
{
struct ListNode* tmp = head;
head = head->next;
free(tmp);
}
struct ListNode* temp = head;
while (temp != NULL && temp->next != NULL)
{
if (temp->next->val == val)
{
struct ListNode* cur = temp->next;
temp->next = cur->next;
free(cur);
}
else
{
temp = temp->next;
}
}
return head;
}
复 杂 度 分 析
时 间 复 杂 度:O (n),其 中 n 为 链 表 长 度。需 遍 历 每 个 节 点 一 次。
空 间 复 杂 度:O (1),仅 需 常 数 级 额 外 空 间 存 储 指 针 变 量。
以 链 表 元 素 为 1,2,6,3,4,5,6 val 为 6 进 行 动 画 演 示。

方 法 二 - - - 双 指 针
思 路 分 析
从 单 链 表 中 删 除 所 有 值 等 于 给 定 值 val 的 节 点。主 要 思 路 是 通 过 双 指 针 prev 和 cur 遍 历 链 表,当 遇 到 目 标 节 点 时,调 整 指 针 跳 过 该 节 点 并 释 放 内 存。
温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。
下 面 展 示代 码 示 例
。
javascript
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* prev = NULL;
struct ListNode* cur = head;
while (cur != NULL)
{
if (cur->val != val)
{
prev = cur;
cur = cur->next;
}
else
{
if (cur == head && cur->val == val)
{
head = cur->next;
free(cur);
cur = head;
}
else
{
prev->next = cur->next;
free(cur);
cur = prev->next;
}
}
}
return head;
}
代 码 分 析
1、初 始 化 双 指 针 :
prev:初 始 化 为 NULL,用 于 记 录 当 前 节 点 的 前 一 个 节 点。
cur:初 始 化 为 头 节 点 head,用 于 遍 历 链 表。
2、遍 历 链 表 :
使 用 while (cur != NULL) 循 环 遍 历 链 表,直 到 所 有 节 点 处 理 完 毕。
3、处 理 当 前 节 点 :
(1)若 当 前 节 点 值 不 等 于 val:
将 prev 更 新 为 cur。
cur 后 移 一 位(cur = cur->next)。
(2)若 当 前 节 点 值 等 于 val:
若 当 前 节 点 是 头 节 点 cur == head:
将 头 指 针 head 后 移 一 位 head = cur->next。
释 放 当 前 节 点 内 存 free(cur)。
更 新 cur 为 新 的 头 节 点 cur = head。
若 当 前 节 点 不 是 头 节 点:
通 过 prev->next = cur->next 跳 过 当 前 节 点。
释 放 当 前 节 点 内 存 free(cur)。
更 新 cur 为 prev->next。
4、返 回 头 指 针:
遍 历 结 束 后,所 有 值 为 val 的 节 点 均 被 删 除,返 回 新 的 头 指 针 head。
复 杂 度 分 析
时 间 复 杂 度:O (n),其 中 n 为 链 表 长 度。需 遍 历 每 个 节 点 一 次。
空 间 复 杂 度:O (1),仅 需 常 数 级 额 外 空 间 存 储 指 针 变 量。
方 法 三 - - - 尾 插
思 路 分 析
通 过 遍 历 原 链 表,将 不 等 于 val 的 节 点 依 次 尾 插 到 新 链 表 中,最 后 返 回 新 链 表 的 头 指 针。
温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。
下 面 展 示代 码 示 例
。
javascript
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* cur = head;
struct ListNode* newhead = NULL;
struct ListNode* tail = NULL;
while (cur)
{
if (cur->val != val)
{
// 尾插
if (tail == NULL)
{
newhead = tail = cur;
}
else
{
tail->next = cur;
tail = tail->next;
}
cur = cur->next;
}
else
{
struct ListNode* next = cur->next;
free(cur);
cur = next;
}
}
if(tail)
{
tail->next=NULL;
}
return newhead;
}
代 码 分 析
1、初 始 化 变 量:
cur:用 于 遍 历 原 链 表 的 指 针,初 始 化 为 原 链 表 头 head。
newhead:新 链 表 的 头 指 针,初 始 化 为 NULL。
tail:新 链 表 的 尾 指 针,初 始 化 为 NULL,用 于 高 效 尾 插 节 点。
2、遍 历 原 链 表:
使 用 while (cur) 循 环 遍 历 原 链 表,直 到 所 有 节 点 处 理 完 毕。
3、处 理 当 前 节 点:
(1)若 当 前 节 点 值 不 等 于 val:
若 新 链 表 为 空(tail == NULL):
将 newhead 和 tail 都 指 向 当 前 节 点 cur。
若 新 链 表 非 空:
将 当 前 节 点 cur 连 接 到 新 链 表 尾 部(tail->next = cur)。
更 新 tail 为 当 前 节 点(tail = tail->next)。
移 动 cur 到 下 一 个 节 点。
(2)若 当 前 节 点 值 等 于 val:
保 存 当 前 节 点 的 下 一 个 节 点 指 针 next。
释 放 当 前 节 点 内 存(free(cur)),防 止 内 存 泄 漏。
将 cur 更 新 为 next,继 续 处 理 后 续 节 点。
4、处 理 尾 节 点 的 next 指 针:
遍 历 结 束 后,确 保 新 链 表 的 尾 节 点 tail 的 next 指 针 为 NULL,避 免 形 成 环。
5、返 回 新 链 表 头 指 针:
返 回 newhead,即 新 链 表 的 头 节 点。若 原 链 表 所 有 节 点 均 被 删 除,newhead 为 NULL。
复 杂 度 分 析
时 间 复 杂 度:O (n),其 中 n 为 链 表 长 度。需 遍 历 每 个 节 点 一 次。
空 间 复 杂 度:O (1),仅 需 常 数 级 额 外 空 间 存 储 指 针 变 量。
第 二 题 - - - 反 转 链 表
描 述
给 你 单 链 表 的 头 节 点 head ,请 你 反 转 链 表,并 返 回 反 转 后 的 链 表。
示 例 1
输 入:head = [1,2,3,4,5]
输 出:[5,4,3,2,1]
示 例 2
输 入:head = [1,2]
输 出:[2,1]
示 例 3:
输 入:head = []
输 出:[ ]
提 示:
链 表 中 节 点 的 数 目 范 围 是 [0, 5000]
-5000 <= Node.val <= 5000
方 法 一 - - - 迭 代
思 路 分 析
使 用 三 个 指 针 协 同 工 作,逐 步 将 每 个 节 点 的 next 指 针 指 向 前 驱 节 点,最 终 实 现 链 表 反 转。
温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。
下 面 展 示代 码 示 例
。
javascript
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode* n1 = NULL;
struct ListNode* n2 = head;
struct ListNode* tmp = head;
while(tmp!=NULL)
{
tmp = n2->next;
n2->next=n1;
n1=n2;
n2=tmp;
}
return n1;
}
代 码 分 析
1、初 始 化 三 个 指 针:
(1)n1:初 始 化 为 NULL,用 于 指 向 当 前 处 理 节 点 的 前 驱 节 点。
(2)n2:初 始 化 为 head,指 向 当 前 正 在 处 理 的 节 点。
(3)tmp:初 始 化 为 head,作 为 临 时 指 针,用 于 保 存 后 续 节 点。
2、遍 历 链 表:
使 用 while (tmp != NULL) 循 环,确 保 遍 历 完 整 个 链 表。
3、反 转 操 作:
(1)保 存 后 继 节 点:tmp = n2->next,保 存 当 前 节 点 的 下 一 个 节 点。
(2)调 整 指 针 方 向:n2->next = n1,将 当 前 节 点 的 next 指 向 前 驱 节 点。
(3)指 针 后 移:
n1 移 动 到 当 前 节 点(n1 = n2)。
n2 移 动 到 之 前 保 存 的 后 继 节 点(n2 = tmp)。
4、返 回 新 头 节 点:
循 环 结 束 后,n1 指 向 原 链 表 的 最 后 一 个 节 点,即 反 转 后 的 新 头 节 点,返 回 n1。
复 杂 度 分 析
时 间 复 杂 度 :O (n),需 遍 历 每 个 节 点 一 次。
空 间 复 杂 度:O (1),仅 需 常 数 级 额 外 空 间 存 储 指 针 变 量。
方 法 二 - - - 采 用 头 插 创 建 新 链 表
思 路 分 析
遍 历 原 链 表,为 每 个 节 点 创 建 副 本,创 建 新 的 链 表,然 后 将 副 本 节 点 依 次 插 入 到 新 链 表 的 头 部,最 终 形 成 原 链 表 的 逆 序。
温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。
下 面 展 示代 码 示 例
。
javascript
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode* cur = head;
struct ListNode* newHead = NULL; //新链表的头指针
while (cur)
{
struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
if (newNode == NULL)
{
perror("malloc failed");
// 释放已分配的节点
while (newHead)
{
struct ListNode* temp = newHead;
newHead = newHead->next;
free(temp);
}
return NULL;
}
newNode->val = cur->val;
newNode->next = newHead; //头插法:新节点指向当前头
newHead = newNode; //更新头指针
cur = cur->next;
}
return newHead; //返回新链表的头结点
}
代 码 分 析
1、初 始 化 变 量:
cur:指 向 原 链 表 当 前 节 点,初 始 化 为 head。
newHead:新 链 表 的 头 指 针,初 始 化 为 NULL。
2、遍 历 原 链 表:
使 用 while (cur) 循 环,直 到 原 链 表 遍 历 完 毕。
3、创 建 新 节 点 并 头 插:
(1)分 配 内 存:为 当 前 节 点 创 建 副 本 newNode。
(2)赋 值:将 原 节 点 的 值 赋 给 新 节 点。
(3)头 插 操 作:
将 新 节 点 的 next 指 向 当 前 新 链 表 的 头 结 点 newHead。
更 新 newHead 为 新 节 点,使 其 成 为 新 链 表 的 头 结 点。
4、错 误 处 理:
若 内 存 分 配 失 败(newNode == NULL):
释 放 已 创 建 的 新 链 表 节 点(避 免 内 存 泄 漏)。
返 回 NULL 表 示 操 作 失 败。
5、返 回 结 果:
遍 历 结 束 后,newHead 指 向 新 链 表 的 头 节 点,返 回 newHead。
关 键 技 巧 与 注 意 事 项
头 插 法 的 逆 序 特 性:
每 次 将 新 节 点 插 入 到 链 表 头 部,导 致 新 链 表 节 点 顺 序 与 插 入 顺 序 相 反。
内 存 管 理:
每 个 新 节 点 都 通 过 malloc 分 配 内 存,需 确 保 使 用 完 毕 后 调 用 free 释 放。
当 内 存 分 配 失 败 时,需 释 放 已 分 配 的 所 有 新 节 点,避 免 内 存 泄 漏。
原 链 表 不 变:
该 方 法 不 修 改 原 链 表 结 构,适 合 需 要 保 留 原 链 表 的 场 景。
复 杂 度 分 析
时 间 复 杂 度:O (n),遍 历 原 链 表 一 次。
空 间 复 杂 度:O (n),需 要 创 建 新 链 表。

总 结
至 此,关 于 链 表 的 探 索 暂 告 一 段 落,但 你 的 编 程 征 程 才 刚 刚 启 航。编 写 代 码 是 与 计 算 机 逻 辑 深 度 对 话,过 程 中 虽 会 在 结 构 设 计、算 法 实 现 的 困 境 里 挣 扎,但 这 些 磨 砺 加 深 了 对 代 码 逻 辑 和 数 据 组 织 的 理 解。愿 你 合 上 电 脑 后,灵 感 不 断,在 数 据 结 构 的 世 界 里 持 续 深 耕,书 写 属 于 自 己 的 编 程 传 奇,下 一 次 开 启,定 有 全 新 的 精 彩 等 待。小 编 期 待 重 逢,盼 下 次 阅 读 时 见 证 你 们 更 大 的 进 步,共 赴 代 码 之 约!