CodeTop按顺序刷题0003:LeetCode 206. 反转链表(迭代法图文详解与动态演示)

CodeTop按顺序刷题0003:LeetCode 206. 反转链表(迭代法图文详解与动态演示)

大家好!欢迎来到我的 CodeTop 按顺序刷题 合集。今天我们来攻克高频面试题:206. 反转链表。

在面试中,链表反转是极其基础且高频的手撕代码题。如果对指针的指向不够敏感,很容易在遍历时把链表弄"断"或者陷入死循环。今天我们用迭代法 来彻底搞懂它,并且文末附带了可交互的代码同步动画,带你一眼看清每一个指针的动态变化!


一、 题目描述与核心思想

  • 题目链接 :LeetCode 206. 反转链表
  • 题目要求 :给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

核心思想:迭代法(双指针 + 临时指针)

假设链表为 1 → 2 → 3 → ∅ 1 \to 2 \to 3 \to \varnothing 1→2→3→∅,我们想要把它改成 ∅ ← 1 ← 2 ← 3 \varnothing \leftarrow 1 \leftarrow 2 \leftarrow 3 ∅←1←2←3。

在遍历链表时,将当前节点的 next 指针改为指向前一个节点。由于链表节点没有引用其前一个节点,因此我们必须:

  1. 事先存储其前一个节点 (prev)。
  2. 在更改引用之前,必须先存储后一个节点 (next),否则链表断开后后面的节点就找不到了!
  3. 用 curr 表示当前正在遍历的节点,循环推进直到 curr 变为空,最后返回新的头引用 prev。

二、 C++ 完整代码

cpp 复制代码
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr; // 前驱节点,初始化为 nullptr
        ListNode* curr = head;   // 当前节点,从头节点开始
        while (curr) {
            ListNode* next = curr->next; // 1. 暂存当前节点的下一个节点,防止链表断开丢失
            curr->next = prev;           // 2. 将当前节点的 next 指向 prev(实现反转)
            prev = curr;                 // 3. prev 往后移(来到当前位置)
            curr = next;                 // 4. curr 往后移(来到刚才暂存的 next 位置)
        }
        return prev; // 循环结束后,prev 指向新的头节点
    }
};

三、 💡 核心可视化动画演示(HTML源码)

为了让你彻底看懂代码和指针是如何同步跑起来的,我制作了一个代码高亮与链表动态演变同步的交互式 HTML 动画。

你可以将下方代码复制并保存为 reverse_linked_list.html 文件,然后用任意浏览器双击打开,点击"下一步"亲自动手体验整个反转过程:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>单链表反转(迭代法)代码同步演示</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background-color: #f7f9fa;
            color: #333;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 20px;
            margin: 0;
        }
        h2 { margin-bottom: 5px; color: #ffa116; }
        .subtitle { font-size: 14px; color: #666; margin-bottom: 20px; }
        
        .main-layout {
            display: flex;
            gap: 20px;
            width: 900px;
            max-width: 100%;
        }

        .card {
            background: white;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.05);
            padding: 20px;
            flex: 1;
            display: flex;
            flex-direction: column;
        }

        .card-title {
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 15px;
            color: #1a1a1a;
            border-bottom: 2px solid #f0f0f0;
            padding-bottom: 8px;
        }

        .container {
            display: flex;
            align-items: center;
            justify-content: center;
            flex-wrap: wrap;
            gap: 15px;
            min-height: 140px;
            padding: 10px 0;
        }

        .node-wrapper {
            display: flex;
            flex-direction: column;
            align-items: center;
            position: relative;
        }

        .pointers {
            height: 25px;
            font-size: 11px;
            font-weight: bold;
            display: flex;
            gap: 4px;
            margin-bottom: 5px;
        }

        .ptr-tag {
            padding: 1px 5px;
            border-radius: 4px;
            color: white;
            display: none;
        }
        .ptr-prev { background-color: #ff4d4f; }
        .ptr-curr { background-color: #1890ff; }
        .ptr-next { background-color: #52c41a; }

        .node-box {
            display: flex;
            align-items: center;
            background: white;
            border: 2px solid #d9d9d9;
            border-radius: 8px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.03);
        }

        .node-val {
            width: 40px;
            height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 16px;
            font-weight: bold;
            border-right: 1px solid #d9d9d9;
        }

        .node-next {
            width: 30px;
            height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 11px;
            color: #8c8c8c;
        }

        .arrow {
            font-size: 18px;
            color: #8c8c8c;
            font-weight: bold;
        }

        .null-node {
            padding: 8px 12px;
            background: #e8e8e8;
            border-radius: 6px;
            font-weight: bold;
            font-size: 13px;
            color: #595959;
        }

        .status-panel {
            display: flex;
            justify-content: space-around;
            background: #f6f8fa;
            padding: 10px;
            border-radius: 6px;
            margin-bottom: 15px;
            font-size: 13px;
            font-weight: bold;
        }
        .status-item span { font-weight: normal; color: #1890ff; }

        pre {
            background: #1e1e1e;
            color: #d4d4d4;
            padding: 15px;
            border-radius: 6px;
            font-family: Consolas, Monaco, monospace;
            font-size: 13px;
            line-height: 1.5;
            overflow-x: auto;
            margin: 0;
        }
        .code-line {
            display: block;
            padding: 0 4px;
            border-radius: 3px;
        }
        .code-line.active {
            background-color: #264f78;
            color: #ffffff;
            font-weight: bold;
        }

        .controls {
            display: flex;
            gap: 15px;
            margin-top: 20px;
            justify-content: center;
        }

        button {
            padding: 10px 25px;
            font-size: 15px;
            font-weight: bold;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            background-color: #ffa116;
            color: white;
            box-shadow: 0 2px 6px rgba(255,161,22,0.3);
            transition: background 0.2s;
        }
        button:hover { background-color: #e69110; }
        button:disabled { background-color: #d9d9d9; cursor: not-allowed; box-shadow: none; }

        .explanation {
            margin: 15px auto 0 auto;
            background: #fffbe6;
            border: 1px solid #ffe58f;
            padding: 12px 20px;
            border-radius: 6px;
            width: 860px;
            font-size: 14px;
            color: #d46b08;
            line-height: 1.4;
        }
    </style>
</head>
<body>

    <h2>LeetCode 206. 反转链表 - 代码与动画同步演示</h2>
    <div class="subtitle">左侧看链表与指针动态变化,右侧看 C++ 代码实时高亮</div>

    <div style="display: flex; justify-content: center;">
        <div class="main-layout">
            <div class="card">
                <div class="card-title">🔗 链表可视化区</div>
                <div class="status-panel" id="statusPanel">
                    <div class="status-item">prev: <span id="valPrev">nullptr</span></div>
                    <div class="status-item">curr: <span id="valCurr">1</span></div>
                    <div class="status-item">next: <span id="valNext">未定义</span></div>
                </div>
                <div class="container" id="linkedListContainer"></div>
            </div>

            <div class="card">
                <div class="card-title">💻 C++ 代码同步高亮</div>
                <pre><code><span id="line-3" class="code-line">    ListNode* reverseList(ListNode* head) {</span>
<span id="line-4" class="code-line">        ListNode* prev = nullptr;</span>
<span id="line-5" class="code-line">        ListNode* curr = head;</span>
<span id="line-6" class="code-line">        while (curr) {</span>
<span id="line-7" class="code-line">            ListNode* next = curr->next;</span>
<span id="line-8" class="code-line">            curr->next = prev;</span>
<span id="line-9" class="code-line">            prev = curr;</span>
<span id="line-10" class="code-line">            curr = next;</span>
<span id="line-11" class="code-line">        }</span>
<span id="line-12" class="code-line">        return prev;</span>
<span id="line-13" class="code-line">    }</span></code></pre>
            </div>
        </div>
    </div>

    <div class="controls">
        <button id="prevStepBtn" onclick="prevStep()" disabled>上一步</button>
        <button id="nextStepBtn" onclick="nextStep()">下一步</button>
        <button onclick="resetDemo()">重置</button>
    </div>

    <div class="explanation" id="explanationText" style="max-width: 860px; box-sizing: border-box;">
        💡 准备就绪:点击"下一步"开始执行初始化代码。
    </div>

<script>
    let steps = [
        {
            line: 4,
            state: [
                { val: 1, nextPtr: 2, isReversed: false },
                { val: 2, nextPtr: 3, isReversed: false },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: null, curr: 0, next: null },
            textVals: { prev: "nullptr", curr: "节点 1", next: "nullptr (未初始化)" },
            text: "执行第 4 行:初始化 prev 指针指向 nullptr。"
        },
        {
            line: 5,
            state: [
                { val: 1, nextPtr: 2, isReversed: false },
                { val: 2, nextPtr: 3, isReversed: false },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: null, curr: 0, next: null },
            textVals: { prev: "nullptr", curr: "节点 1", next: "nullptr (未初始化)" },
            text: "执行第 5 行:初始化 curr 指针指向头节点 1。"
        },
        {
            line: 6,
            state: [
                { val: 1, nextPtr: 2, isReversed: false },
                { val: 2, nextPtr: 3, isReversed: false },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: null, curr: 0, next: null },
            textVals: { prev: "nullptr", curr: "节点 1", next: "nullptr" },
            text: "进入 while(curr) 循环:curr 不为空(当前是节点 1),开始执行循环体。"
        },
        {
            line: 7,
            state: [
                { val: 1, nextPtr: 2, isReversed: false },
                { val: 2, nextPtr: 3, isReversed: false },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: null, curr: 0, next: 1 },
            textVals: { prev: "nullptr", curr: "节点 1", next: "节点 2" },
            text: "执行第 7 行 [ListNode* next = curr->next]:用 next 暂存节点 2,防止断开后找不到了!"
        },
        {
            line: 8,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 3, isReversed: false },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: null, curr: 0, next: 1 },
            textVals: { prev: "nullptr", curr: "节点 1", next: "节点 2" },
            text: "执行第 8 行 [curr->next = prev]:修改节点 1 的 next 指向 prev(nullptr),实现箭头反转。"
        },
        {
            line: 9,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 3, isReversed: false },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: 0, curr: 0, next: 1 },
            textVals: { prev: "节点 1", curr: "节点 1", next: "节点 2" },
            text: "执行第 9 行 [prev = curr]:prev 往右移,变成当前节点 1。"
        },
        {
            line: 10,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 3, isReversed: false },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: 0, curr: 1, next: 1 },
            textVals: { prev: "节点 1", curr: "节点 2", next: "节点 2" },
            text: "执行第 10 行 [curr = next]:curr 往右移,通过暂存的 next 走到节点 2。第一轮循环结束!"
        },
        {
            line: 6,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 3, isReversed: false },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: 0, curr: 1, next: 1 },
            textVals: { prev: "节点 1", curr: "节点 2", next: "节点 2" },
            text: "再次进入 while 循环:curr 现在是节点 2(不为空)。"
        },
        {
            line: 7,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 3, isReversed: false },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: 0, curr: 1, next: 2 },
            textVals: { prev: "节点 1", curr: "节点 2", next: "节点 3" },
            text: "执行第 7 行:用 next 暂存节点 2 的下一个节点(即节点 3)。"
        },
        {
            line: 8,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 1, isReversed: true },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: 0, curr: 1, next: 2 },
            textVals: { prev: "节点 1", curr: "节点 2", next: "节点 3" },
            text: "执行第 8 行:把节点 2 的 next 指向 prev(节点 1),实现反转。"
        },
        {
            line: 9,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 1, isReversed: true },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: 1, curr: 1, next: 2 },
            textVals: { prev: "节点 2", curr: "节点 2", next: "节点 3" },
            text: "执行第 9 行:prev 往右移动到节点 2。"
        },
        {
            line: 10,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 1, isReversed: true },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: 1, curr: 2, next: 2 },
            textVals: { prev: "节点 2", curr: "节点 3", next: "节点 3" },
            text: "执行第 10 行:curr 往右移动到节点 3。第二轮循环结束!"
        },
        {
            line: 6,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 1, isReversed: true },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: 1, curr: 2, next: 2 },
            textVals: { prev: "节点 2", curr: "节点 3", next: "节点 3" },
            text: "再次进入 while 循环:curr 现在是节点 3(不为空)。"
        },
        {
            line: 7,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 1, isReversed: true },
                { val: 3, nextPtr: 'null', isReversed: false }
            ],
            pointers: { prev: 1, curr: 2, next: 'null' },
            textVals: { prev: "节点 2", curr: "节点 3", next: "nullptr" },
            text: "执行第 7 行:用 next 暂存节点 3 的下一个节点(nullptr)。"
        },
        {
            line: 8,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 1, isReversed: true },
                { val: 3, nextPtr: 2, isReversed: true }
            ],
            pointers: { prev: 1, curr: 2, next: 'null' },
            textVals: { prev: "节点 2", curr: "节点 3", next: "nullptr" },
            text: "执行第 8 行:把节点 3 的 next 指向 prev(节点 2),实现反转。"
        },
        {
            line: 9,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 1, isReversed: true },
                { val: 3, nextPtr: 2, isReversed: true }
            ],
            pointers: { prev: 2, curr: 2, next: 'null' },
            textVals: { prev: "节点 3", curr: "节点 3", next: "nullptr" },
            text: "执行第 9 行:prev 往右移动到节点 3。"
        },
        {
            line: 10,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 1, isReversed: true },
                { val: 3, nextPtr: 2, isReversed: true }
            ],
            pointers: { prev: 2, curr: null, next: 'null' },
            textVals: { prev: "节点 3", curr: "nullptr", next: "nullptr" },
            text: "执行第 10 行:curr 走到 next 的位置(nullptr)。第三轮循环结束!"
        },
        {
            line: 6,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 1, isReversed: true },
                { val: 3, nextPtr: 2, isReversed: true }
            ],
            pointers: { prev: 2, curr: null, next: 'null' },
            textVals: { prev: "节点 3", curr: "nullptr", next: "nullptr" },
            text: "再次判断 while(curr):此时 curr 为 nullptr,条件不成立,跳出循环。"
        },
        {
            line: 12,
            state: [
                { val: 1, nextPtr: 'null', isReversed: true },
                { val: 2, nextPtr: 1, isReversed: true },
                { val: 3, nextPtr: 2, isReversed: true }
            ],
            pointers: { prev: 2, curr: null, next: 'null' },
            textVals: { prev: "节点 3", curr: "nullptr", next: "nullptr" },
            text: "执行第 12 行 [return prev]:返回新的头节点(节点 3)。链表反转成功!"
        }
    ];

    let currentStepIndex = 0;

    function render() {
        const step = steps[currentStepIndex];
        document.querySelectorAll('.code-line').forEach(el => el.classList.remove('active'));
        const activeLineEl = document.getElementById(`line-${step.line}`);
        if (activeLineEl) activeLineEl.classList.add('active');

        document.getElementById('valPrev').innerText = step.textVals.prev;
        document.getElementById('valCurr').innerText = step.textVals.curr;
        document.getElementById('valNext').innerText = step.textVals.next;

        const container = document.getElementById('linkedListContainer');
        container.innerHTML = '';

        step.state.forEach((node, idx) => {
            const wrapper = document.createElement('div');
            wrapper.className = 'node-wrapper';

            const ptrsDiv = document.createElement('div');
            ptrsDiv.className = 'pointers';
            
            if (step.pointers.prev === idx) {
                const p = document.createElement('span');
                p.className = 'ptr-tag ptr-prev';
                p.style.display = 'inline-block';
                p.innerText = 'prev';
                ptrsDiv.appendChild(p);
            }
            if (step.pointers.curr === idx) {
                const c = document.createElement('span');
                c.className = 'ptr-tag ptr-curr';
                c.style.display = 'inline-block';
                c.innerText = 'curr';
                ptrsDiv.appendChild(c);
            }
            if (step.pointers.next === idx) {
                const n = document.createElement('span');
                n.className = 'ptr-tag ptr-next';
                n.style.display = 'inline-block';
                n.innerText = 'next';
                ptrsDiv.appendChild(n);
            }
            wrapper.appendChild(ptrsDiv);

            const box = document.createElement('div');
            box.className = 'node-box';
            box.innerHTML = `<div class="node-val">${node.val}</div><div class="node-next">next</div>`;
            wrapper.appendChild(box);

            container.appendChild(wrapper);

            if (idx < step.state.length - 1) {
                const arrow = document.createElement('div');
                arrow.className = 'arrow';
                if (node.isReversed) {
                    arrow.innerHTML = '←';
                    arrow.style.color = '#ff4d4f';
                } else {
                    arrow.innerHTML = '→';
                }
                container.appendChild(arrow);
            }
        });

        const nullNode = document.createElement('div');
        nullNode.className = 'null-node';
        nullNode.innerText = 'Ø (null)';
        container.appendChild(nullNode);

        document.getElementById('explanationText').innerText = step.text;
        document.getElementById('prevStepBtn').disabled = currentStepIndex === 0;
        document.getElementById('nextStepBtn').disabled = currentStepIndex === steps.length - 1;
    }

    function nextStep() {
        if (currentStepIndex < steps.length - 1) {
            currentStepIndex++;
            render();
        }
    }

    function prevStep() {
        if (currentStepIndex > 0) {
            currentStepIndex--;
            render();
        }
    }

    function resetDemo() {
        currentStepIndex = 0;
        render();
    }

    render();
</script>
</body>
</html>

四、 复杂度分析

  • 时间复杂度 : O ( n ) O(n) O(n),其中 n n n 是链表的长度。需要遍历链表中的每个节点一次。
  • 空间复杂度 : O ( 1 ) O(1) O(1),只需要常数级别的额外空间来存储 prev、curr 和 next 指针。

希望这篇图文和动态演示能帮你彻底搞懂反转链表的迭代写法!欢迎大家关注我的 CodeTop 按顺序刷题 合集,我们下题再见!

相关推荐
果霸大叔1 小时前
AI网关第8篇:AI 代理与负载均衡--多模型实例如何调度?
人工智能·算法·自然语言处理·负载均衡·ai-native
不会就选b1 小时前
算法日常・每日刷题--<动态规划>29
数据结构·算法·leetcode
垆边人似月.2 小时前
最少的元素个数使和为 target
数据结构·c++·算法
炽橙超真云2 小时前
2D 工程图纸如何自动转成可编辑 3D 模型?拆解读图与建体两条技术路径(DXF/DWG/PDF 到 STEP)
算法·3d·工业智能·工业ai
夕除2 小时前
redis--dict rehash
算法·哈希算法
云上先途12 小时前
Agent 轨迹数据一般包含哪些内容,加工的核心目标是什么?
人工智能·算法·机器学习
佳木逢钺3 小时前
从火灾模拟到系统热寂:一个双非运维如何用零 GPU 推导出多智能体系统的终极死锁
人工智能·python·算法·多智能体
垆边人似月.3 小时前
网格最小代价路径,总代价不超过 budget
算法
虚无的纽扣3 小时前
【力扣刷题】第五天:将 x 减到 0 的最小操作数、最大连续1的个数 III
算法·leetcode·职场和发展