|  | 
|  | 1 | +<!DOCTYPE html> | 
|  | 2 | +<html lang="zh-CN"> | 
|  | 3 | +<head> | 
|  | 4 | +<meta charset="UTF-8" /> | 
|  | 5 | +<meta name="viewport" content="width=device-width, initial-scale=1" /> | 
|  | 6 | +<title>T164 最大间距桶排序可视化</title> | 
|  | 7 | +<style> | 
|  | 8 | +  body { | 
|  | 9 | +    font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; | 
|  | 10 | +    margin: 20px; | 
|  | 11 | +    background: #f5f5f5; | 
|  | 12 | +  } | 
|  | 13 | +  h1 { | 
|  | 14 | +    text-align: center; | 
|  | 15 | +  } | 
|  | 16 | +  a#homeLink { | 
|  | 17 | +    display: inline-block; | 
|  | 18 | +    margin-bottom: 15px; | 
|  | 19 | +    text-decoration: none; | 
|  | 20 | +    font-weight: bold; | 
|  | 21 | +    color: #007acc; | 
|  | 22 | +  } | 
|  | 23 | +  .input-area { | 
|  | 24 | +    margin-bottom: 15px; | 
|  | 25 | +    text-align: center; | 
|  | 26 | +  } | 
|  | 27 | +  input[type="text"] { | 
|  | 28 | +    width: 60%; | 
|  | 29 | +    padding: 6px 10px; | 
|  | 30 | +    font-size: 16px; | 
|  | 31 | +  } | 
|  | 32 | +  input[type="number"] { | 
|  | 33 | +    width: 60px; | 
|  | 34 | +    padding: 4px; | 
|  | 35 | +    font-size: 16px; | 
|  | 36 | +  } | 
|  | 37 | +  button { | 
|  | 38 | +    margin-left: 10px; | 
|  | 39 | +    padding: 6px 12px; | 
|  | 40 | +    font-size: 16px; | 
|  | 41 | +    cursor: pointer; | 
|  | 42 | +  } | 
|  | 43 | +  #arrayContainer { | 
|  | 44 | +    margin: 20px auto; | 
|  | 45 | +    width: 90%; | 
|  | 46 | +    display: flex; | 
|  | 47 | +    justify-content: center; | 
|  | 48 | +    gap: 6px; | 
|  | 49 | +    flex-wrap: wrap; | 
|  | 50 | +  } | 
|  | 51 | +  .array-item { | 
|  | 52 | +    width: 40px; | 
|  | 53 | +    height: 40px; | 
|  | 54 | +    background: #3498db; | 
|  | 55 | +    color: white; | 
|  | 56 | +    display: flex; | 
|  | 57 | +    justify-content: center; | 
|  | 58 | +    align-items: center; | 
|  | 59 | +    border-radius: 6px; | 
|  | 60 | +    font-weight: bold; | 
|  | 61 | +    user-select: none; | 
|  | 62 | +    transition: background-color 0.3s; | 
|  | 63 | +  } | 
|  | 64 | +  .highlight { | 
|  | 65 | +    background-color: #e74c3c !important; | 
|  | 66 | +  } | 
|  | 67 | +  .bucket { | 
|  | 68 | +    border: 2px dashed #888; | 
|  | 69 | +    border-radius: 6px; | 
|  | 70 | +    padding: 10px; | 
|  | 71 | +    margin: 10px; | 
|  | 72 | +    min-width: 60px; | 
|  | 73 | +    min-height: 40px; | 
|  | 74 | +    text-align: center; | 
|  | 75 | +    background-color: #ecf0f1; | 
|  | 76 | +    position: relative; | 
|  | 77 | +  } | 
|  | 78 | +  .bucket-label { | 
|  | 79 | +    font-size: 12px; | 
|  | 80 | +    margin-bottom: 4px; | 
|  | 81 | +    color: #555; | 
|  | 82 | +  } | 
|  | 83 | +  #bucketsContainer { | 
|  | 84 | +    display: flex; | 
|  | 85 | +    justify-content: center; | 
|  | 86 | +    flex-wrap: wrap; | 
|  | 87 | +    margin-top: 20px; | 
|  | 88 | +    gap: 6px; | 
|  | 89 | +  } | 
|  | 90 | +  #log { | 
|  | 91 | +    background-color: #111; | 
|  | 92 | +    color: #eee; | 
|  | 93 | +    font-family: monospace; | 
|  | 94 | +    padding: 12px; | 
|  | 95 | +    height: 200px; | 
|  | 96 | +    overflow-y: auto; | 
|  | 97 | +    white-space: pre-wrap; | 
|  | 98 | +    border-radius: 6px; | 
|  | 99 | +    margin-top: 20px; | 
|  | 100 | +  } | 
|  | 101 | +  #infoArea { | 
|  | 102 | +    margin-top: 15px; | 
|  | 103 | +    font-size: 16px; | 
|  | 104 | +    text-align: center; | 
|  | 105 | +    min-height: 24px; | 
|  | 106 | +  } | 
|  | 107 | +</style> | 
|  | 108 | +</head> | 
|  | 109 | +<body> | 
|  | 110 | +  <h1>力扣 T164 最大间距桶排序可视化</h1> | 
|  | 111 | +  <a href="index.html" id="homeLink">【返回首页】</a> | 
|  | 112 | +  <div class="input-area"> | 
|  | 113 | +    <label for="inputArray">输入数组(逗号分隔): </label> | 
|  | 114 | +    <input type="text" id="inputArray" value="3,6,9,1,24,25,48" /> | 
|  | 115 | +    <label for="intervalInput">动画间隔(秒):</label> | 
|  | 116 | +    <input type="number" id="intervalInput" value="1" min="0.1" step="0.1" /> | 
|  | 117 | +    <button id="startBtn">开始可视化</button> | 
|  | 118 | +  </div> | 
|  | 119 | + | 
|  | 120 | +  <div id="arrayContainer"></div> | 
|  | 121 | +  <div id="bucketsContainer"></div> | 
|  | 122 | + | 
|  | 123 | +  <div id="infoArea"></div> | 
|  | 124 | +  <div id="log"></div> | 
|  | 125 | + | 
|  | 126 | +<script> | 
|  | 127 | +(() => { | 
|  | 128 | +  const startBtn = document.getElementById("startBtn"); | 
|  | 129 | +  const inputArray = document.getElementById("inputArray"); | 
|  | 130 | +  const intervalInput = document.getElementById("intervalInput"); | 
|  | 131 | +  const arrayContainer = document.getElementById("arrayContainer"); | 
|  | 132 | +  const bucketsContainer = document.getElementById("bucketsContainer"); | 
|  | 133 | +  const logArea = document.getElementById("log"); | 
|  | 134 | +  const infoArea = document.getElementById("infoArea"); | 
|  | 135 | + | 
|  | 136 | +  let animationInterval = 1000; | 
|  | 137 | +  let timerId = null; | 
|  | 138 | + | 
|  | 139 | +  // 渲染数组 | 
|  | 140 | +  function renderArray(arr, highlightIndices = []) { | 
|  | 141 | +    arrayContainer.innerHTML = ""; | 
|  | 142 | +    arr.forEach((v, i) => { | 
|  | 143 | +      const div = document.createElement("div"); | 
|  | 144 | +      div.className = "array-item"; | 
|  | 145 | +      div.textContent = v; | 
|  | 146 | +      if (highlightIndices.includes(i)) { | 
|  | 147 | +        div.classList.add("highlight"); | 
|  | 148 | +      } | 
|  | 149 | +      arrayContainer.appendChild(div); | 
|  | 150 | +    }); | 
|  | 151 | +  } | 
|  | 152 | + | 
|  | 153 | +  // 渲染桶状态 | 
|  | 154 | +  function renderBuckets(buckets, min, bucketWidth, highlightIndex = -1) { | 
|  | 155 | +    bucketsContainer.innerHTML = ""; | 
|  | 156 | +    buckets.forEach((bucket, i) => { | 
|  | 157 | +      const div = document.createElement("div"); | 
|  | 158 | +      div.className = "bucket"; | 
|  | 159 | +      if (i === highlightIndex) { | 
|  | 160 | +        div.style.backgroundColor = "#f39c12"; | 
|  | 161 | +      } else { | 
|  | 162 | +        div.style.backgroundColor = "#ecf0f1"; | 
|  | 163 | +      } | 
|  | 164 | +      const label = document.createElement("div"); | 
|  | 165 | +      label.className = "bucket-label"; | 
|  | 166 | +      label.textContent = `桶${i}`; | 
|  | 167 | +      div.appendChild(label); | 
|  | 168 | + | 
|  | 169 | +      if (bucket[0] === Number.MAX_SAFE_INTEGER) { | 
|  | 170 | +        div.appendChild(document.createTextNode("空")); | 
|  | 171 | +      } else { | 
|  | 172 | +        div.appendChild(document.createTextNode(`最小: ${bucket[0]}, 最大: ${bucket[1]}`)); | 
|  | 173 | +      } | 
|  | 174 | +      bucketsContainer.appendChild(div); | 
|  | 175 | +    }); | 
|  | 176 | +  } | 
|  | 177 | + | 
|  | 178 | +  // 输出日志,自动滚动到底部 | 
|  | 179 | +  function log(msg) { | 
|  | 180 | +    logArea.textContent += msg + "\n"; | 
|  | 181 | +    logArea.scrollTop = logArea.scrollHeight; | 
|  | 182 | +  } | 
|  | 183 | + | 
|  | 184 | +  // 清理日志和信息 | 
|  | 185 | +  function resetUI() { | 
|  | 186 | +    logArea.textContent = ""; | 
|  | 187 | +    infoArea.textContent = ""; | 
|  | 188 | +    bucketsContainer.innerHTML = ""; | 
|  | 189 | +    arrayContainer.innerHTML = ""; | 
|  | 190 | +  } | 
|  | 191 | + | 
|  | 192 | +  // 主动画函数:桶排序可视化 | 
|  | 193 | +  async function visualizeMaximumGap(arr, intervalSec) { | 
|  | 194 | +    resetUI(); | 
|  | 195 | +    if (arr.length < 2) { | 
|  | 196 | +      infoArea.textContent = "数组长度小于2,无需排序,最大间距为0"; | 
|  | 197 | +      log("数组长度小于2,直接返回0"); | 
|  | 198 | +      renderArray(arr); | 
|  | 199 | +      return; | 
|  | 200 | +    } | 
|  | 201 | + | 
|  | 202 | +    log(`输入数组: [${arr.join(", ")}]`); | 
|  | 203 | +    renderArray(arr); | 
|  | 204 | + | 
|  | 205 | +    let min = Number.MAX_SAFE_INTEGER; | 
|  | 206 | +    let max = Number.MIN_SAFE_INTEGER; | 
|  | 207 | +    for (const num of arr) { | 
|  | 208 | +      if (num > max) max = num; | 
|  | 209 | +      if (num < min) min = num; | 
|  | 210 | +    } | 
|  | 211 | +    log(`最小值: ${min}, 最大值: ${max}`); | 
|  | 212 | + | 
|  | 213 | +    if (min === max) { | 
|  | 214 | +      log("最小值和最大值相同,最大间距为0"); | 
|  | 215 | +      infoArea.textContent = "所有元素相同,最大间距为0"; | 
|  | 216 | +      return; | 
|  | 217 | +    } | 
|  | 218 | + | 
|  | 219 | +    const bucketSize = arr.length + 1; | 
|  | 220 | +    let buckets = new Array(bucketSize); | 
|  | 221 | +    for (let i = 0; i < bucketSize; i++) { | 
|  | 222 | +      // 用大整数代替Integer.MAX_VALUE | 
|  | 223 | +      buckets[i] = [Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER]; | 
|  | 224 | +    } | 
|  | 225 | + | 
|  | 226 | +    // 计算桶宽度 | 
|  | 227 | +    const bucketWidth = Math.ceil((max - min) / bucketSize); | 
|  | 228 | +    log(`桶数量: ${bucketSize}, 桶宽度: ${bucketWidth}`); | 
|  | 229 | + | 
|  | 230 | +    // 显示初始空桶 | 
|  | 231 | +    renderBuckets(buckets, min, bucketWidth); | 
|  | 232 | + | 
|  | 233 | +    // 放入桶中 | 
|  | 234 | +    for (let i = 0; i < arr.length; i++) { | 
|  | 235 | +      const num = arr[i]; | 
|  | 236 | +      let bucketIndex = Math.floor((num - min) / bucketWidth); | 
|  | 237 | +      if (bucketIndex >= bucketSize) bucketIndex = bucketSize - 1; | 
|  | 238 | + | 
|  | 239 | +      infoArea.textContent = `第${i + 1}个元素 ${num} 放入桶 ${bucketIndex}`; | 
|  | 240 | +      log(`元素 ${num} 计算桶索引: ${bucketIndex}`); | 
|  | 241 | + | 
|  | 242 | +      // 高亮当前元素 | 
|  | 243 | +      renderArray(arr, [i]); | 
|  | 244 | +      // 高亮当前桶 | 
|  | 245 | +      renderBuckets(buckets, min, bucketWidth, bucketIndex); | 
|  | 246 | + | 
|  | 247 | +      // 更新桶内最大最小值 | 
|  | 248 | +      buckets[bucketIndex][0] = Math.min(buckets[bucketIndex][0], num); | 
|  | 249 | +      buckets[bucketIndex][1] = Math.max(buckets[bucketIndex][1], num); | 
|  | 250 | + | 
|  | 251 | +      log(`更新桶${bucketIndex} => 最小值: ${buckets[bucketIndex][0]}, 最大值: ${buckets[bucketIndex][1]}`); | 
|  | 252 | + | 
|  | 253 | +      await sleep(intervalSec); | 
|  | 254 | +    } | 
|  | 255 | + | 
|  | 256 | +    // 计算最大间距 | 
|  | 257 | +    let maxGap = 0; | 
|  | 258 | +    let prev = min; | 
|  | 259 | +    infoArea.textContent = "开始计算最大间距..."; | 
|  | 260 | +    renderBuckets(buckets, min, bucketWidth); | 
|  | 261 | + | 
|  | 262 | +    for (let i = 0; i < buckets.length; i++) { | 
|  | 263 | +      if (buckets[i][0] === Number.MAX_SAFE_INTEGER) { | 
|  | 264 | +        log(`桶${i}为空,跳过`); | 
|  | 265 | +        continue; | 
|  | 266 | +      } | 
|  | 267 | + | 
|  | 268 | +      let gap = buckets[i][0] - prev; | 
|  | 269 | +      log(`桶${i}的最小值 ${buckets[i][0]} - 上一个最大值 ${prev} = 间距 ${gap}`); | 
|  | 270 | +      if (gap > maxGap) { | 
|  | 271 | +        maxGap = gap; | 
|  | 272 | +        log(`更新最大间距为 ${maxGap}`); | 
|  | 273 | +      } | 
|  | 274 | + | 
|  | 275 | +      prev = buckets[i][1]; | 
|  | 276 | + | 
|  | 277 | +      infoArea.textContent = `检查桶${i},当前最大间距: ${maxGap}`; | 
|  | 278 | +      renderBuckets(buckets, min, bucketWidth, i); | 
|  | 279 | + | 
|  | 280 | +      await sleep(intervalSec); | 
|  | 281 | +    } | 
|  | 282 | + | 
|  | 283 | +    infoArea.textContent = `最大间距计算完成: ${maxGap}`; | 
|  | 284 | +    log(`返回最大间距: ${maxGap}`); | 
|  | 285 | + | 
|  | 286 | +    // 最后高亮全部数组 | 
|  | 287 | +    renderArray(arr); | 
|  | 288 | +    renderBuckets(buckets, min, bucketWidth); | 
|  | 289 | +  } | 
|  | 290 | + | 
|  | 291 | +  function sleep(seconds) { | 
|  | 292 | +    return new Promise(resolve => setTimeout(resolve, seconds * 1000)); | 
|  | 293 | +  } | 
|  | 294 | + | 
|  | 295 | +  // 事件绑定 | 
|  | 296 | +  startBtn.addEventListener("click", () => { | 
|  | 297 | +    if (timerId) { | 
|  | 298 | +      clearTimeout(timerId); | 
|  | 299 | +      timerId = null; | 
|  | 300 | +    } | 
|  | 301 | +    const rawInput = inputArray.value.trim(); | 
|  | 302 | +    if (!rawInput) { | 
|  | 303 | +      alert("请输入有效数组!"); | 
|  | 304 | +      return; | 
|  | 305 | +    } | 
|  | 306 | +    let arr = rawInput.split(",").map(s => parseInt(s.trim())).filter(n => !isNaN(n)); | 
|  | 307 | +    if (arr.length === 0) { | 
|  | 308 | +      alert("请输入有效数字数组!"); | 
|  | 309 | +      return; | 
|  | 310 | +    } | 
|  | 311 | +    let intervalSec = parseFloat(intervalInput.value); | 
|  | 312 | +    if (isNaN(intervalSec) || intervalSec <= 0) { | 
|  | 313 | +      intervalSec = 1; | 
|  | 314 | +    } | 
|  | 315 | +    animationInterval = intervalSec; | 
|  | 316 | + | 
|  | 317 | +    visualizeMaximumGap(arr, animationInterval); | 
|  | 318 | +  }); | 
|  | 319 | + | 
|  | 320 | +  // 页面加载时默认渲染数组 | 
|  | 321 | +  renderArray(inputArray.value.split(",").map(s => parseInt(s.trim())).filter(n => !isNaN(n))); | 
|  | 322 | +})(); | 
|  | 323 | +</script> | 
|  | 324 | +</body> | 
|  | 325 | +</html> | 
0 commit comments