LeetCode 算 法 实 战 - - - 移 除 链 表 元 素、反 转 链 表

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),需 要 创 建 新 链 表。

总 结

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

相关推荐
蒙奇D索大24 分钟前
【数据结构】图论核心算法解析:深度优先搜索(DFS)的纵深遍历与生成树实战指南
数据结构·算法·深度优先·图论·图搜索算法
让我们一起加油好吗31 分钟前
【基础算法】高精度(加、减、乘、除)
c++·算法·高精度·洛谷
不会敲代码的灵长类38 分钟前
机器学习算法-k-means
算法·机器学习·kmeans
Studying 开龙wu38 分钟前
机器学习有监督学习sklearn实战二:六种算法对鸢尾花(Iris)数据集进行分类和特征可视化
学习·算法·机器学习
鑫鑫向栄1 小时前
[蓝桥杯]缩位求和
数据结构·c++·算法·职场和发展·蓝桥杯
Tony__Ferguson1 小时前
简述八大排序(Sort)
数据结构·算法·排序算法
芜湖xin1 小时前
【题解-洛谷】P9422 [蓝桥杯 2023 国 B] 合并数列
算法·队列
鑫鑫向栄1 小时前
[蓝桥杯]外卖店优先级
数据结构·c++·算法·职场和发展·蓝桥杯
<但凡.1 小时前
题海拾贝:P8598 [蓝桥杯 2013 省 AB] 错误票据
数据结构·算法·蓝桥杯
wangyuxuan10292 小时前
AtCoder Beginner Contest 399题目翻译
开发语言·c++·算法