|  | 
|  | 1 | +<!DOCTYPE html> | 
|  | 2 | +<html lang="en"> | 
|  | 3 | +<head> | 
|  | 4 | +  <meta charset="UTF-8"> | 
|  | 5 | +  <title>LC160 双指针链表相交可视化</title> | 
|  | 6 | +  <style> | 
|  | 7 | +    body { | 
|  | 8 | +      font-family: "Segoe UI", sans-serif; | 
|  | 9 | +      background: #0f172a; | 
|  | 10 | +      color: #e2e8f0; | 
|  | 11 | +      padding: 40px; | 
|  | 12 | +    } | 
|  | 13 | +    h2 { | 
|  | 14 | +      text-align: center; | 
|  | 15 | +      color: #38bdf8; | 
|  | 16 | +    } | 
|  | 17 | +    input { | 
|  | 18 | +      padding: 10px; | 
|  | 19 | +      width: 300px; | 
|  | 20 | +      margin-right: 10px; | 
|  | 21 | +      border-radius: 5px; | 
|  | 22 | +      border: none; | 
|  | 23 | +    } | 
|  | 24 | +    button { | 
|  | 25 | +      padding: 10px 20px; | 
|  | 26 | +      background: #3b82f6; | 
|  | 27 | +      border: none; | 
|  | 28 | +      border-radius: 5px; | 
|  | 29 | +      color: white; | 
|  | 30 | +      font-weight: bold; | 
|  | 31 | +      cursor: pointer; | 
|  | 32 | +      margin-top: 10px; | 
|  | 33 | +    } | 
|  | 34 | +    .list-container { | 
|  | 35 | +      display: flex; | 
|  | 36 | +      align-items: center; | 
|  | 37 | +      margin: 20px 0; | 
|  | 38 | +      overflow-x: auto; | 
|  | 39 | +    } | 
|  | 40 | +    .node { | 
|  | 41 | +      padding: 10px 15px; | 
|  | 42 | +      margin-right: 10px; | 
|  | 43 | +      border-radius: 8px; | 
|  | 44 | +      background: #1e293b; | 
|  | 45 | +      border: 2px solid #334155; | 
|  | 46 | +      position: relative; | 
|  | 47 | +      min-width: 40px; | 
|  | 48 | +      text-align: center; | 
|  | 49 | +      transition: transform 0.3s ease; | 
|  | 50 | +    } | 
|  | 51 | +    .arrow::after { | 
|  | 52 | +      content: '→'; | 
|  | 53 | +      position: absolute; | 
|  | 54 | +      right: -15px; | 
|  | 55 | +      top: 50%; | 
|  | 56 | +      transform: translateY(-50%); | 
|  | 57 | +      color: #94a3b8; | 
|  | 58 | +    } | 
|  | 59 | +    .highlight { | 
|  | 60 | +      background: #facc15 !important; | 
|  | 61 | +      color: #000; | 
|  | 62 | +      animation: blink 1s infinite; | 
|  | 63 | +      border-color: #facc15; | 
|  | 64 | +    } | 
|  | 65 | +    .shared { | 
|  | 66 | +      background: #10b981; | 
|  | 67 | +      border-color: #22c55e; | 
|  | 68 | +    } | 
|  | 69 | +    .overlap { | 
|  | 70 | +      background: #c084fc !important; | 
|  | 71 | +      border-color: #d946ef !important; | 
|  | 72 | +    } | 
|  | 73 | +    .both-highlight { | 
|  | 74 | +      background: #e11d48 !important; | 
|  | 75 | +      border-color: #f43f5e !important; | 
|  | 76 | +      animation: flash 1s infinite; | 
|  | 77 | +      color: white; | 
|  | 78 | +    } | 
|  | 79 | +    .log { | 
|  | 80 | +      background: #1e293b; | 
|  | 81 | +      border: 1px solid #334155; | 
|  | 82 | +      border-radius: 8px; | 
|  | 83 | +      padding: 10px; | 
|  | 84 | +      max-height: 200px; | 
|  | 85 | +      overflow-y: auto; | 
|  | 86 | +      margin-top: 20px; | 
|  | 87 | +      white-space: pre-wrap; | 
|  | 88 | +    } | 
|  | 89 | +    @keyframes blink { | 
|  | 90 | +      0% { box-shadow: 0 0 10px #facc15; } | 
|  | 91 | +      50% { box-shadow: 0 0 20px #facc15; } | 
|  | 92 | +      100% { box-shadow: 0 0 10px #facc15; } | 
|  | 93 | +    } | 
|  | 94 | +    @keyframes flash { | 
|  | 95 | +      0%, 100% { box-shadow: 0 0 10px #f43f5e; } | 
|  | 96 | +      50% { box-shadow: 0 0 20px #f43f5e; } | 
|  | 97 | +    } | 
|  | 98 | + | 
|  | 99 | +    .back-btn { | 
|  | 100 | +      position: fixed; | 
|  | 101 | +      top: 20px; | 
|  | 102 | +      left: 20px; | 
|  | 103 | +      background: #10b981; | 
|  | 104 | +    } | 
|  | 105 | +  </style> | 
|  | 106 | +</head> | 
|  | 107 | +<body> | 
|  | 108 | +  <button class="back-btn" onclick="location.href='index.html'">← 返回首页</button> | 
|  | 109 | + | 
|  | 110 | +  <h2>🚀 LC160 双指针链表相交可视化</h2> | 
|  | 111 | + | 
|  | 112 | +  <div style="text-align:center;"> | 
|  | 113 | +    链表 A: <input id="inputA" placeholder="1,2,3,8,9" value="1,2,3,8,9" /> | 
|  | 114 | +    链表 B: <input id="inputB" placeholder="4,5,8,9" value="4,5,8,9" /> | 
|  | 115 | +    <br> | 
|  | 116 | +    <button onclick="startVisualization()">开始可视化</button> | 
|  | 117 | +  </div> | 
|  | 118 | + | 
|  | 119 | +  <h3>链表 A</h3> | 
|  | 120 | +  <div id="listA" class="list-container"></div> | 
|  | 121 | + | 
|  | 122 | +  <h3>链表 B</h3> | 
|  | 123 | +  <div id="listB" class="list-container"></div> | 
|  | 124 | + | 
|  | 125 | +  <h3>执行日志</h3> | 
|  | 126 | +  <div id="log" class="log"></div> | 
|  | 127 | + | 
|  | 128 | +  <script> | 
|  | 129 | +    let nodesA = [], nodesB = []; | 
|  | 130 | +    let pointerA = 0, pointerB = 0; | 
|  | 131 | +    let timer = null; | 
|  | 132 | +    let step = 1; | 
|  | 133 | + | 
|  | 134 | +    function buildLists(valA, valB) { | 
|  | 135 | +      let arrA = valA.split(',').map(s => s.trim()); | 
|  | 136 | +      let arrB = valB.split(',').map(s => s.trim()); | 
|  | 137 | + | 
|  | 138 | +      let i = arrA.length - 1, j = arrB.length - 1; | 
|  | 139 | +      const shared = []; | 
|  | 140 | +      while (i >= 0 && j >= 0 && arrA[i] === arrB[j]) { | 
|  | 141 | +        shared.unshift(arrA[i]); | 
|  | 142 | +        i--; j--; | 
|  | 143 | +      } | 
|  | 144 | + | 
|  | 145 | +      const build = (unique, shared) => | 
|  | 146 | +        unique.map(v => ({ val: v, shared: false })) | 
|  | 147 | +              .concat(shared.map(v => ({ val: v, shared: true }))); | 
|  | 148 | + | 
|  | 149 | +      return [ | 
|  | 150 | +        build(arrA.slice(0, i + 1), shared), | 
|  | 151 | +        build(arrB.slice(0, j + 1), shared) | 
|  | 152 | +      ]; | 
|  | 153 | +    } | 
|  | 154 | + | 
|  | 155 | +    function renderLists() { | 
|  | 156 | +      const listA = document.getElementById("listA"); | 
|  | 157 | +      const listB = document.getElementById("listB"); | 
|  | 158 | +      listA.innerHTML = ""; | 
|  | 159 | +      listB.innerHTML = ""; | 
|  | 160 | + | 
|  | 161 | +      nodesA.forEach((node, idx) => { | 
|  | 162 | +        const div = document.createElement("div"); | 
|  | 163 | +        let className = "node arrow"; | 
|  | 164 | +        if (node.shared) className += " shared"; | 
|  | 165 | +        if (idx === pointerA && idx < nodesA.length) { | 
|  | 166 | +          if (pointerB < nodesB.length && nodesB[pointerB].val === node.val && node.shared) { | 
|  | 167 | +            className += " both-highlight"; // 🎯 重合节点高亮 | 
|  | 168 | +          } else if (node.shared) { | 
|  | 169 | +            className += " overlap"; // 🟣 路过共享节点 | 
|  | 170 | +          } else { | 
|  | 171 | +            className += " highlight"; | 
|  | 172 | +          } | 
|  | 173 | +        } | 
|  | 174 | +        div.className = className; | 
|  | 175 | +        div.textContent = node.val; | 
|  | 176 | +        listA.appendChild(div); | 
|  | 177 | +      }); | 
|  | 178 | + | 
|  | 179 | +      nodesB.forEach((node, idx) => { | 
|  | 180 | +        const div = document.createElement("div"); | 
|  | 181 | +        let className = "node arrow"; | 
|  | 182 | +        if (node.shared) className += " shared"; | 
|  | 183 | +        if (idx === pointerB && idx < nodesB.length) { | 
|  | 184 | +          if (pointerA < nodesA.length && nodesA[pointerA].val === node.val && node.shared) { | 
|  | 185 | +            className += " both-highlight"; // 🎯 重合节点高亮 | 
|  | 186 | +          } else if (node.shared) { | 
|  | 187 | +            className += " overlap"; // 🟣 路过共享节点 | 
|  | 188 | +          } else { | 
|  | 189 | +            className += " highlight"; | 
|  | 190 | +          } | 
|  | 191 | +        } | 
|  | 192 | +        div.className = className; | 
|  | 193 | +        div.textContent = node.val; | 
|  | 194 | +        listB.appendChild(div); | 
|  | 195 | +      }); | 
|  | 196 | +    } | 
|  | 197 | + | 
|  | 198 | +    function log(msg) { | 
|  | 199 | +      const logDiv = document.getElementById("log"); | 
|  | 200 | +      logDiv.textContent += msg + "\n"; | 
|  | 201 | +      logDiv.scrollTop = logDiv.scrollHeight; | 
|  | 202 | +    } | 
|  | 203 | + | 
|  | 204 | +    function nextStep() { | 
|  | 205 | +      const nodeA = nodesA[pointerA] || null; | 
|  | 206 | +      const nodeB = nodesB[pointerB] || null; | 
|  | 207 | + | 
|  | 208 | +      const valA = nodeA ? nodeA.val : "null"; | 
|  | 209 | +      const valB = nodeB ? nodeB.val : "null"; | 
|  | 210 | + | 
|  | 211 | +      log(`Step ${step++}: p1 → ${valA}, p2 → ${valB}`); | 
|  | 212 | + | 
|  | 213 | +      if (nodeA && nodeB && nodeA.shared && nodeB.shared && nodeA.val === nodeB.val) { | 
|  | 214 | +        log(`🎉 找到相交节点:${nodeA.val}`); | 
|  | 215 | +        renderLists(); | 
|  | 216 | +        clearInterval(timer); | 
|  | 217 | +        return; | 
|  | 218 | +      } | 
|  | 219 | + | 
|  | 220 | +      pointerA++; | 
|  | 221 | +      if (pointerA > nodesA.length) pointerA = 0; | 
|  | 222 | + | 
|  | 223 | +      pointerB++; | 
|  | 224 | +      if (pointerB > nodesB.length) pointerB = 0; | 
|  | 225 | + | 
|  | 226 | +      renderLists(); | 
|  | 227 | +    } | 
|  | 228 | + | 
|  | 229 | +    function startVisualization() { | 
|  | 230 | +      const valA = document.getElementById("inputA").value || "1,2,3,8,9"; | 
|  | 231 | +      const valB = document.getElementById("inputB").value || "4,5,8,9"; | 
|  | 232 | + | 
|  | 233 | +      [nodesA, nodesB] = buildLists(valA, valB); | 
|  | 234 | +      pointerA = 0; | 
|  | 235 | +      pointerB = 0; | 
|  | 236 | +      step = 1; | 
|  | 237 | + | 
|  | 238 | +      document.getElementById("log").textContent = ""; | 
|  | 239 | +      renderLists(); | 
|  | 240 | + | 
|  | 241 | +      if (timer) clearInterval(timer); | 
|  | 242 | +      timer = setInterval(nextStep, 1000); | 
|  | 243 | +    } | 
|  | 244 | +  </script> | 
|  | 245 | +</body> | 
|  | 246 | +</html> | 
0 commit comments